From 145f7167948d9b9333e5098d62c1dc5b2a2bd3a0 Mon Sep 17 00:00:00 2001 From: defnax <9952056+defnax@users.noreply.github.com> Date: Fri, 14 Aug 2026 20:42:12 +0200 Subject: [PATCH 1/8] Added required changes to can use on mobile makeHidden --- src/jsonapi/jsonapi.cpp | 7 ++++++- src/retroshare/rsinit.h | 4 +++- src/rsserver/rsinit.cc | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/jsonapi/jsonapi.cpp b/src/jsonapi/jsonapi.cpp index 2d91ee36f..078db5739 100644 --- a/src/jsonapi/jsonapi.cpp +++ b/src/jsonapi/jsonapi.cpp @@ -235,6 +235,8 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), std::string locationName; std::string pgpName; std::string password; + bool makeHidden = false; + bool makeAutoTor = false; // JSON API only std::string apiUser; @@ -249,6 +251,8 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), RS_SERIAL_PROCESS(locationName); RS_SERIAL_PROCESS(pgpName); RS_SERIAL_PROCESS(password); + RS_SERIAL_PROCESS(makeHidden); + RS_SERIAL_PROCESS(makeAutoTor); // JSON API only RS_SERIAL_PROCESS(apiUser); @@ -265,7 +269,8 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), if(!retval) // call retroshare C++ API retval = rsLoginHelper->createLocationV2( - locationId, pgpId, locationName, pgpName, password ); + locationId, pgpId, locationName, pgpName, password, + makeHidden, makeAutoTor ); if(!retval) retval = authorizeUser(apiUser, apiPass); diff --git a/src/retroshare/rsinit.h b/src/retroshare/rsinit.h index 494c1b57c..e5c02259e 100644 --- a/src/retroshare/rsinit.h +++ b/src/retroshare/rsinit.h @@ -498,7 +498,9 @@ public: RsPgpId& pgpId, const std::string& locationName, const std::string& pgpName, - const std::string& password + const std::string& password, + bool makeHidden = false, + bool makeAutoTor = false /* JSON API only * const std::string& apiUser * const std::string& apiPass */ ); diff --git a/src/rsserver/rsinit.cc b/src/rsserver/rsinit.cc index 31d6e08c4..7e08c84cc 100644 --- a/src/rsserver/rsinit.cc +++ b/src/rsserver/rsinit.cc @@ -2236,7 +2236,8 @@ void RsLoginHelper::getLocations(std::vector& store) std::error_condition RsLoginHelper::createLocationV2( RsPeerId& locationId, RsPgpId& pgpId, const std::string& locationName, const std::string& pgpName, - const std::string& password ) + const std::string& password, + bool makeHidden, bool makeAutoTor ) { if(isLoggedIn()) return RsInitErrorNum::ALREADY_LOGGED_IN; if(locationName.empty()) return RsInitErrorNum::INVALID_LOCATION_NAME; @@ -2256,7 +2257,7 @@ std::error_condition RsLoginHelper::createLocationV2( RsLoginHandler::cachePgpPassphrase(password); bool ret = RsAccounts::createNewAccount( - pgpId, "", locationName, "", false, false, sslPassword, + pgpId, "", locationName, "", makeHidden, makeAutoTor, sslPassword, locationId, errorMessage ); if(!ret) { From 6499ffbae68b1ece4d69a4db764ae7d917a90eb8 Mon Sep 17 00:00:00 2001 From: defnax <9952056+defnax@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:58:06 +0200 Subject: [PATCH 2/8] Changes for Tor API --- src/retroshare/rstor.h | 15 +++++ src/tor/TorManager.cpp | 127 ++++++++++++++++++++++++++--------------- src/tor/TorManager.h | 5 ++ 3 files changed, 101 insertions(+), 46 deletions(-) diff --git a/src/retroshare/rstor.h b/src/retroshare/rstor.h index fe96348a6..37704d640 100644 --- a/src/retroshare/rstor.h +++ b/src/retroshare/rstor.h @@ -128,6 +128,21 @@ public: */ static RsTorConnectivityStatus torConnectivityStatus() ; + /** + * Configure TorManager to attach to an already running Tor instance. + * This must be called before start(). The external process is never + * stopped or taken over by RetroShare. + * + * This endpoint is available before account login because the Tor + * connection must be configured before hidden-node initialization starts. + * + * @jsonapi{development,unauthenticated} + */ + static bool setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ); + static void setTorDataDirectory(const std::string& dir); static void setHiddenServiceDirectory(const std::string& dir); diff --git a/src/tor/TorManager.cpp b/src/tor/TorManager.cpp index f4bb6d27e..44a31e341 100644 --- a/src/tor/TorManager.cpp +++ b/src/tor/TorManager.cpp @@ -74,6 +74,12 @@ public: bool configNeeded; bool mVerbose; std::string userProvidedTorExecutablePath; + bool useExternalTor; + std::string externalControlAddress; + uint16_t externalControlPort; + ByteArray externalControlPassword; + std::string externalSocksAddress; + uint16_t externalSocksPort; HiddenService *hiddenService ; @@ -123,6 +129,9 @@ TorManagerPrivate::TorManagerPrivate(TorManager *parent) , control(new TorControl()) , configNeeded(false) , mVerbose(false) + , useExternalTor(false) + , externalControlPort(0) + , externalSocksPort(0) , hiddenService(NULL) { control->set_statusChanged_callback([this](int new_status,int /*old_status*/) { controlStatusChanged(new_status); }); @@ -371,6 +380,32 @@ std::string TorManager::torExecutablePath() const return (d==nullptr)?(std::string()):(d->torExecutablePath()); } +bool TorManager::setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ) +{ + if(isRunning()) + { + d->setError("Cannot change Tor connection while TorManager is running."); + return false; + } + if(controlAddress.empty() || controlPort == 0 || + socksAddress.empty() || socksPort == 0) + { + d->setError("External Tor control and SOCKS endpoints must be valid."); + return false; + } + + d->useExternalTor = true; + d->externalControlAddress = controlAddress; + d->externalControlPort = controlPort; + d->externalControlPassword = ByteArray(controlPassword); + d->externalSocksAddress = socksAddress; + d->externalSocksPort = socksPort; + return true; +} + const std::list& TorManager::logMessages() const { return d->logMessages; @@ -394,43 +429,14 @@ bool TorManager::startTorManager() //emit errorChanged(); // not needed because there's no error to handle } -#ifdef TODO - SettingsObject settings("tor"); - - // If a control port is defined by config or environment, skip launching tor - if (!settings.read("controlPort").isUndefined() || - !qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) + if(d->useExternalTor) { - QHostAddress address(settings.read("controlAddress").toString()); - quint16 port = (quint16)settings.read("controlPort").toInt(); - QByteArray password = settings.read("controlPassword").toString().toLatin1(); - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_HOST")) - address = QHostAddress(qgetenv("TOR_CONTROL_HOST")); - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) { - bool ok = false; - port = qgetenv("TOR_CONTROL_PORT").toUShort(&ok); - if (!ok) - port = 0; - } - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PASSWD")) - password = qgetenv("TOR_CONTROL_PASSWD"); - - if (!port) { - d->setError("Invalid control port settings from environment or configuration"); - return false; - } - - if (address.isNull()) - address = QHostAddress::LocalHost; - - d->control->setAuthPassword(password); - d->control->connect(address, port); + RsInfo() << "Connecting to external Tor control port at " + << d->externalControlAddress << ":" << d->externalControlPort; + d->control->setAuthPassword(d->externalControlPassword); + d->control->connect(d->externalControlAddress, d->externalControlPort); } else -#endif { // Launch a bundled Tor instance std::string executable = d->torExecutablePath(); @@ -503,7 +509,8 @@ bool TorManager::startTorManager() void TorManager::run() { - d->process->start(); + if(d->process) + d->process->start(); while(!shouldStop()) { @@ -512,7 +519,8 @@ void TorManager::run() } d->control->shutdownSync(); - d->process->stop(); + if(d->process) + d->process->stop(); if(rsEvents) { @@ -526,10 +534,12 @@ void TorManager::threadTick() { static bool authenticated_msg_already_given = false; - d->process->tick(); - - if(d->process->state() != TorProcess::Ready) - return; + if(d->process) + { + d->process->tick(); + if(d->process->state() != TorProcess::Ready) + return; + } switch(d->control->status()) { @@ -538,8 +548,10 @@ void TorManager::threadTick() break; case TorControl::NotConnected: - RsInfo() << "Connecting to tor process at " << d->process->controlHost() << ":" << d->process->controlPort() << "..." ; - d->control->connect(d->process->controlHost(),d->process->controlPort()); + if(d->useExternalTor) + d->control->connect(d->externalControlAddress,d->externalControlPort); + else + d->control->connect(d->process->controlHost(),d->process->controlPort()); break; case TorControl::SocketConnected: @@ -551,7 +563,8 @@ void TorManager::threadTick() setupHiddenService(); } - d->control->setAuthPassword(d->process->controlPassword()); + d->control->setAuthPassword(d->useExternalTor + ? d->externalControlPassword : d->process->controlPassword()); d->control->authenticate(); RsInfo() << "Authenticating..." ; break; @@ -588,6 +601,12 @@ bool TorManager::getProxyServerInfo(std::string& proxy_server_adress,uint16_t& p proxy_server_adress = control()->socksAddress(); proxy_server_port = control()->socksPort(); + if(d->useExternalTor && (proxy_server_adress.empty() || proxy_server_port == 0)) + { + proxy_server_adress = d->externalSocksAddress; + proxy_server_port = d->externalSocksPort; + } + return proxy_server_port > 1023 ; } @@ -796,7 +815,17 @@ void TorManagerPrivate::setError(const std::string &message) bool RsTor::isTorAvailable() { - return !instance()->d->torExecutablePath().empty(); + return instance()->d->useExternalTor || !instance()->d->torExecutablePath().empty(); +} + +bool RsTor::setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ) +{ + return instance()->setExternalTorConnection( + controlAddress, controlPort, controlPassword, + socksAddress, socksPort ); } bool RsTor::getHiddenServiceInfo(std::string& service_id, @@ -840,11 +869,17 @@ void RsTor::setTorExecutablePath(const std::string& e) std::string RsTor::socksAddress() { - return instance()->control()->socksAddress(); + std::string address; + uint16_t port = 0; + instance()->getProxyServerInfo(address, port); + return address; } uint16_t RsTor::socksPort() { - return instance()->control()->socksPort(); + std::string address; + uint16_t port = 0; + instance()->getProxyServerInfo(address, port); + return port; } static RsTorStatus torStatus(Tor::TorControl::TorStatus t) diff --git a/src/tor/TorManager.h b/src/tor/TorManager.h index a37d833c6..6d173fa4c 100644 --- a/src/tor/TorManager.h +++ b/src/tor/TorManager.h @@ -62,6 +62,11 @@ public: std::string torExecutablePath() const; void setTorExecutablePath(const std::string& tor_exe_full_path) ; + bool setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ); + std::string hiddenServiceDirectory() const; void setHiddenServiceDirectory(const std::string &path); From bbb0f0e40c1cfd2eca36e761d1587096beb293d1 Mon Sep 17 00:00:00 2001 From: defnax <9952056+defnax@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:02:27 +0200 Subject: [PATCH 3/8] Fixed missed api changes --- src/retroshare/rstor.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/retroshare/rstor.h b/src/retroshare/rstor.h index 37704d640..943e96a25 100644 --- a/src/retroshare/rstor.h +++ b/src/retroshare/rstor.h @@ -136,6 +136,15 @@ public: * This endpoint is available before account login because the Tor * connection must be configured before hidden-node initialization starts. * + * @param[in] controlAddress Tor control listener address, usually + * `127.0.0.1` + * @param[in] controlPort Tor control listener TCP port + * @param[in] controlPassword Tor control password, or an empty string for + * null/cookie authentication + * @param[in] socksAddress Tor SOCKS listener address, usually `127.0.0.1` + * @param[in] socksPort Tor SOCKS listener TCP port + * @return true if the external Tor configuration was accepted + * * @jsonapi{development,unauthenticated} */ static bool setExternalTorConnection( From f33977edf37ce189884cb84acf2d1a8c7a278fbf Mon Sep 17 00:00:00 2001 From: defnax <9952056+defnax@users.noreply.github.com> Date: Mon, 17 Aug 2026 10:07:51 +0200 Subject: [PATCH 4/8] Fix crash on mobile app --- src/tor/TorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tor/TorManager.cpp b/src/tor/TorManager.cpp index 44a31e341..5233de7c7 100644 --- a/src/tor/TorManager.cpp +++ b/src/tor/TorManager.cpp @@ -998,7 +998,7 @@ TorManager *RsTor::instance() { #ifdef __APPLE__ assert(pthread_main_np() != 0); // On macOS, ensure we are on the main thread -#elif defined(__linux__) +#elif defined(__linux__) && !defined(__ANDROID__) assert(getpid() == syscall(SYS_gettid)); // On Linux, ensure we are on the main thread #endif From 0a03aed3afa408440d5c217add4e95333cc7fdcb Mon Sep 17 00:00:00 2001 From: defnax <9952056+defnax@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:27:46 +0200 Subject: [PATCH 5/8] enabled getHiddenServiceInfo --- src/retroshare/rstor.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/retroshare/rstor.h b/src/retroshare/rstor.h index 943e96a25..92d34166c 100644 --- a/src/retroshare/rstor.h +++ b/src/retroshare/rstor.h @@ -186,12 +186,14 @@ public: /*! * \brief getHiddenServiceInfo * Gets information about the hidden service setup by RS to run. - * \param service_id - * \param service_onion_address - * \param service_port - * \param service_target_address - * \param target_port - * \return + * \param[out] service_id Tor service identifier without the `.onion` suffix + * \param[out] service_onion_address Complete generated onion hostname + * \param[out] service_port Public onion-service port + * \param[out] service_target_address Local address receiving Tor traffic + * \param[out] target_port Local port receiving Tor traffic + * \return true when an AutoTor hidden service is available + * + * @jsonapi{development} */ static bool getHiddenServiceInfo(std::string& service_id, std::string& service_onion_address, From 2c6334de175777ac27f0e60416f3e68aa0f3b4de Mon Sep 17 00:00:00 2001 From: jolavillette Date: Mon, 31 Aug 2026 20:26:24 +0200 Subject: [PATCH 6/8] Tor: recover when the connection to the control port fails TorControl::connect() sets the status to Connecting before attempting the synchronous TCP connection, but never reset it when the connection failed, so the status stayed at Connecting forever and TorManager::threadTick() (which only retries from the NotConnected state) never attempted to connect again. With an external Tor (e.g. Orbot not started yet, or a wrong control port) this was the main failure mode: the whole auto-Tor login sequence hung forever without reporting any error. TorControl::reconnect() had the same problem, plus it ignored the result of connectToHost() entirely: on success the status stayed at Connecting (so authentication was never attempted after an Error recovery), and on failure it was equally stuck. Now both methods fall back to NotConnected on failure, so the manager thread retries, and reconnect() properly enters SocketConnected on success. --- src/tor/TorControl.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tor/TorControl.cpp b/src/tor/TorControl.cpp index 106244b01..561bbe08a 100644 --- a/src/tor/TorControl.cpp +++ b/src/tor/TorControl.cpp @@ -211,6 +211,8 @@ void TorControl::connect(const std::string &address, uint16_t port) setStatus(SocketConnected); setTorStatus(TorOffline); // connected and running, but not yet ready } + else + setStatus(NotConnected); // so that the owner can try connecting again } void TorControl::reconnect() @@ -221,7 +223,14 @@ void TorControl::reconnect() return; setStatus(Connecting); - mSocket->connectToHost(mTorAddress, mControlPort); + + if(mSocket->connectToHost(mTorAddress, mControlPort)) + { + setStatus(SocketConnected); + setTorStatus(TorOffline); // connected and running, but not yet ready + } + else + setStatus(NotConnected); // so that the owner can try connecting again } void TorControl::authenticateReply(TorControlCommand *sender) From 8e5a92298d5fe57b8007b900ea9f0454f5c969ac Mon Sep 17 00:00:00 2001 From: jolavillette Date: Mon, 31 Aug 2026 20:26:24 +0200 Subject: [PATCH 7/8] Tor: throttle control port connection attempts, make RsTor::instance() thread safe threadTick() runs every 50ms and the connection attempt is synchronous, so retrying from the NotConnected state (now reachable again after a failed attempt) would hammer the control port with up to 20 connections per second. Throttle attempts to one every 2 seconds, and restore the connection log line (with an external-Tor variant) that told which endpoint is being tried. RsTor::instance() lazily creates the TorManager and used platform asserts to require the main thread, which was the guard for that unsynchronized initialisation. But RsTor methods are exposed through the JSON API and thus legitimately reached from restbed worker threads: any /rsTor/* call - including the new setExternalTorConnection endpoint and the auto-Tor login path - aborted debug builds on Linux and Apple platforms (the __ANDROID__ exception covered Android only, not iOS). Protect the initialisation with a mutex instead, and drop the asserts. --- src/tor/TorManager.cpp | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/tor/TorManager.cpp b/src/tor/TorManager.cpp index 5233de7c7..aadb82121 100644 --- a/src/tor/TorManager.cpp +++ b/src/tor/TorManager.cpp @@ -38,12 +38,10 @@ #include "util/rsdir.h" #include "retroshare/rsinit.h" #include +#include #if !defined(_WIN32) && !defined(__MINGW32__) #include #endif -#ifdef __linux__ -#include -#endif #include "TorManager.h" #include "TorProcess.h" @@ -80,6 +78,7 @@ public: ByteArray externalControlPassword; std::string externalSocksAddress; uint16_t externalSocksPort; + time_t lastControlConnectAttempt; HiddenService *hiddenService ; @@ -132,6 +131,7 @@ TorManagerPrivate::TorManagerPrivate(TorManager *parent) , useExternalTor(false) , externalControlPort(0) , externalSocksPort(0) + , lastControlConnectAttempt(0) , hiddenService(NULL) { control->set_statusChanged_callback([this](int new_status,int /*old_status*/) { controlStatusChanged(new_status); }); @@ -548,10 +548,28 @@ void TorManager::threadTick() break; case TorControl::NotConnected: + { + // The connection attempt is synchronous and threadTick() runs every + // 50ms, so throttle retries when the control port is unreachable + // (e.g. an external Tor that is not started yet). + + time_t now = time(nullptr); + + if(now < d->lastControlConnectAttempt + 2) + break; + d->lastControlConnectAttempt = now; + if(d->useExternalTor) + { + RsInfo() << "Connecting to external tor at " << d->externalControlAddress << ":" << d->externalControlPort << "..." ; d->control->connect(d->externalControlAddress,d->externalControlPort); + } else + { + RsInfo() << "Connecting to tor process at " << d->process->controlHost() << ":" << d->process->controlPort() << "..." ; d->control->connect(d->process->controlHost(),d->process->controlPort()); + } + } break; case TorControl::SocketConnected: @@ -990,17 +1008,14 @@ void RsTor::setHiddenServiceDirectory(const std::string& dir) instance()->setHiddenServiceDirectory(dir); } -#ifdef __APPLE__ -#include -#endif - TorManager *RsTor::instance() { -#ifdef __APPLE__ - assert(pthread_main_np() != 0); // On macOS, ensure we are on the main thread -#elif defined(__linux__) && !defined(__ANDROID__) - assert(getpid() == syscall(SYS_gettid)); // On Linux, ensure we are on the main thread -#endif + // RsTor methods are exposed through the JSON API, so this can be reached + // from any thread. Protect the lazy initialisation, which the former + // main-thread asserts used to guard. + + static std::mutex instanceMutex; + std::unique_lock lock(instanceMutex); if(rsTorMgr == nullptr) rsTorMgr = new TorManager; From fb32c62a9a0523ab589a9d11a0824e84bcd4d0cd Mon Sep 17 00:00:00 2001 From: jolavillette Date: Mon, 31 Aug 2026 20:27:49 +0200 Subject: [PATCH 8/8] Tor: fail startAutoTor() when the control port cannot be reached startAutoTor() waited forever for Tor to become ready, with no timeout and no error to break the loop when the control connection could not be established at all (external Tor not running, wrong control port). Since this runs synchronously inside RsLoginHelper::attemptLogin(), a mobile or headless login on an auto-Tor node would simply never return. Give up with an explicit error if the control link has not been seen up once within 30 seconds. Once the control connection has been observed, keep waiting indefinitely for Tor to bootstrap, as before. --- src/rsserver/rsinit.cc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/rsserver/rsinit.cc b/src/rsserver/rsinit.cc index 7e08c84cc..b475351f4 100644 --- a/src/rsserver/rsinit.cc +++ b/src/rsserver/rsinit.cc @@ -2162,6 +2162,15 @@ bool RsInit::startAutoTor() std::string service_id; RsTor::setupHiddenService(); + // If the connection to the Tor control port cannot be established at all + // (e.g. an external Tor that is not running, or a wrong control port), + // fail after a while instead of waiting forever. Once the control link + // has been seen up, wait indefinitely for Tor to bootstrap, as before. + + const time_t control_connect_timeout = 30; // seconds + time_t start_time = time(nullptr); + bool control_link_seen = false; + while(RsTor::torStatus() != RsTorStatus::READY && RsTor::getHiddenServiceStatus(service_id) != RsTorHiddenServiceStatus::ONLINE) // runs until some status is reached: either tor works, or it fails. { rstime::rs_usleep(0.5*1000*1000) ; @@ -2174,6 +2183,24 @@ bool RsInit::startAutoTor() std::cerr << "(EE) Tor hidden service cannot be started: " << error_msg << std::endl; return false; } + + if(!control_link_seen) + switch(RsTor::torConnectivityStatus()) + { + case RsTorConnectivityStatus::SOCKET_CONNECTED: + case RsTorConnectivityStatus::AUTHENTICATING: + case RsTorConnectivityStatus::AUTHENTICATED: + case RsTorConnectivityStatus::HIDDEN_SERVICE_READY: + control_link_seen = true; + break; + default: + if(time(nullptr) > start_time + control_connect_timeout) + { + std::cerr << "(EE) Cannot establish a connection to the Tor control port. Giving up." << std::endl; + return false; + } + break; + } // process Qt event loop to deal with messages of online/offline info // QCoreApplication::processEvents(); }