From 8cab86518cabdce24b5b6e2626ec30c0b98d9820 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Fri, 27 Mar 2026 00:27:22 -0700 Subject: [PATCH] Tor v3: fix hidden service backend, add key persistence and health monitoring Codex changes: delegate hidden service management to the actual Tor backend instead of generating keys the wallet never served. The new AttachToBackendService() reads the hostname Tor creates, and the torrc/process plumbing properly gates HiddenService directives behind the -torhiddenservice flag. Additional fixes: - Back up hs_ed25519_secret_key (96 bytes) to wallet.dat so the onion identity survives deletion of tor_data/ - Restore the key before Tor starts so the same .onion address is regenerated automatically - Add ThreadTorMaintenance: checks Tor health every 30s, auto-restarts with exponential backoff on crash, re-attaches the hidden service and re-registers the onion address with AddLocal() - Seeder maintenance: every 30 min re-announces to peers and refreshes known seeder lists (when -torseeder is enabled) - Clean up ScheduleSeederReannouncement() stub (real work now in thread) - Respect -torsocks port in onion proxy registration Co-Authored-By: Claude Opus 4.6 --- src/init.cpp | 61 +++++++-- src/tor/onion_v3.cpp | 273 ++++++++++++++++++++++++++++++++------- src/tor/onion_v3.h | 8 +- src/tor/tor_embedded.cpp | 58 +++++---- src/tor/tor_embedded.h | 4 +- src/tor/tor_process.cpp | 45 ++++--- src/tor/tor_process.h | 5 +- 7 files changed, 355 insertions(+), 99 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index ea6a553..be90cf2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -355,6 +355,7 @@ std::string HelpMessage() " -tor= " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n" " -notor " + _("Disable Tor startup and .onion connectivity") + "\n" + " -torsocks= " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" + + " -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" + " -torhsport= " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" + //" -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n" + " -port= " + _("Listen for connections on (default: 24112 or testnet: 24111)") + "\n" + @@ -752,7 +753,7 @@ bool AppInit2() // Tor proxy: always configured for .onion connectivity CService addrOnion; - unsigned short const onion_port = 19099; + unsigned short const onion_port = static_cast(GetArg("-torsocks", 19099)); if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") { addrOnion = CService(mapArgs["-tor"], onion_port); @@ -1078,6 +1079,37 @@ bool AppInit2() uiInterface.InitMessage(_("Starting Tor...")); printf("Starting Tor process...\n"); + // Restore hidden service secret key from wallet backup if the key + // file is missing on disk. This preserves the .onion identity even + // if the tor_data directory was deleted. + if (pwalletMain && !GetBoolArg("-notor", false)) { + std::string restoreDataPath = GetArg("-tordatadir", (GetDataDir() / "tor_data").string()); + fs::path secretKeyPath = fs::path(restoreDataPath) / "hidden_service" / "hs_ed25519_secret_key"; + + if (!fs::exists(secretKeyPath)) { + CWalletDB walletdb(pwalletMain->strWalletFile); + std::vector backedUpKey; + + if (walletdb.ReadSetting("tor_v3_hs_secret_key_backup", backedUpKey) && + backedUpKey.size() == 96) { + fs::create_directories(secretKeyPath.parent_path()); + + std::ofstream keyFile(secretKeyPath.string().c_str(), std::ios::binary); + if (keyFile.is_open()) { + keyFile.write(reinterpret_cast(backedUpKey.data()), + backedUpKey.size()); + keyFile.close(); + printf("Restored Tor hidden service secret key from wallet backup\n"); + } else { + printf("WARNING: Failed to write restored hs_ed25519_secret_key to %s\n", + secretKeyPath.string().c_str()); + } + } + + OPENSSL_cleanse(backedUpKey.data(), backedUpKey.size()); + } + } + int64_t nTorStart = GetTimeMillis(); bool torStarted = StartEmbeddedTor(); StartupPerfLog("tor_start", GetTimeMillis() - nTorStart, strprintf("started=%d", torStarted)); @@ -1100,13 +1132,14 @@ bool AppInit2() int64_t nTorIdentityStart = GetTimeMillis(); LoadTorV3Config(); TorV3Config& torConfig = GetTorV3Config(); - torConfig.enableTor = true; - torConfig.enableHiddenService = true; - torConfig.hiddenServicePort = GetListenPort(); + torConfig.enableTor = torStarted; + torConfig.enableHiddenService = torStarted && CTorEmbedded::GetInstance()->IsHiddenServiceEnabled(); + torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort(); torConfig.torDataDirectory = torDataPath; + std::string onionAddr; - if (InitTorV3()) { - string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress(); + if (torConfig.enableTor && torConfig.enableHiddenService && InitTorV3()) { + onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress(); if (!onionAddr.empty()) { // Write onion/hostname for compatibility with existing code paths fs::path onionDir = GetDataDir() / "onion"; @@ -1118,11 +1151,15 @@ bool AppInit2() } // Register onion address as local address for peer discovery - AddLocal(CService(onionAddr, GetListenPort(), fNameLookup), LOCAL_MANUAL); + AddLocal(CService(onionAddr, torConfig.hiddenServicePort, fNameLookup), LOCAL_MANUAL); printf("Tor V3 identity: %s\n", onionAddr.c_str()); } else { printf("WARNING: Tor V3 initialized but no onion address available\n"); } + } else if (torStarted && !torConfig.enableHiddenService) { + printf("Tor hidden service disabled by configuration\n"); + } else if (!torStarted) { + printf("Skipping Tor V3 identity because the Tor backend is unavailable\n"); } else { printf("WARNING: Failed to initialize Tor V3 identity\n"); } @@ -1139,13 +1176,21 @@ bool AppInit2() while (!torOnion.empty() && (torOnion.back() == '\n' || torOnion.back() == '\r' || torOnion.back() == ' ')) torOnion.pop_back(); if (!torOnion.empty()) { - AddLocal(CService(torOnion, GetListenPort(), fNameLookup), LOCAL_MANUAL); + if (torOnion != onionAddr) { + AddLocal(CService(torOnion, torConfig.hiddenServicePort, fNameLookup), LOCAL_MANUAL); + } printf("Tor hidden service (from Tor process): %s\n", torOnion.c_str()); } } } } StartupPerfLog("tor_setup_total", GetTimeMillis() - nTorStart); + + // Launch background thread for Tor health monitoring and seeder maintenance + if (torStarted) { + if (!NewThread(ThreadTorMaintenance, NULL)) + printf("Warning: ThreadTorMaintenance could not be started\n"); + } } // ********************************************************* Step 9: import blocks diff --git a/src/tor/onion_v3.cpp b/src/tor/onion_v3.cpp index 31757cf..3575290 100644 --- a/src/tor/onion_v3.cpp +++ b/src/tor/onion_v3.cpp @@ -18,6 +18,7 @@ #endif #include "onion_v3.h" +#include "tor_embedded.h" #include "tor_crypto_compat.h" #include "../util.h" #include "../net.h" @@ -59,6 +60,53 @@ extern CWallet* pwalletMain; CTorV3Manager* CTorV3Manager::instance = nullptr; static TorV3Config torV3Config; +static boost::filesystem::path GetBackendHiddenServiceDir(const std::string& torDataDir) +{ + return boost::filesystem::path(torDataDir) / "hidden_service"; +} + +static bool ReadTrimmedFirstLine(const boost::filesystem::path& path, std::string& valueOut) +{ + valueOut.clear(); + + std::ifstream file(path.string().c_str()); + if (!file.is_open() || !std::getline(file, valueOut)) { + return false; + } + + while (!valueOut.empty()) { + const char ch = valueOut[valueOut.size() - 1]; + if (ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t') { + break; + } + valueOut.erase(valueOut.size() - 1); + } + + return !valueOut.empty(); +} + +static std::string GetEffectiveTorProxy() +{ + proxyType proxy; + if (GetProxy(NET_TOR, proxy)) { + return proxy.first.ToStringIPPort(); + } + + if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") { + CService torProxy(mapArgs["-tor"], GetArg("-torsocks", 19099)); + if (torProxy.IsValid()) { + return torProxy.ToStringIPPort(); + } + } + + std::string explicitProxy = GetArg("-torproxy", ""); + if (!explicitProxy.empty()) { + return explicitProxy; + } + + return strprintf("127.0.0.1:%d", GetArg("-torsocks", 19099)); +} + // Utility function for proper base32 encoding (RFC 4648) - Tor variant std::string EncodeBase32Proper(const unsigned char* data, size_t len) { @@ -503,40 +551,95 @@ bool CTorV3Service::LoadFromPrivateKey(const std::string& privKey) bool CTorV3Service::StartOnionService() { - if (onionAddress.empty()) { - printf("ERROR: No onion address generated\n"); + if (!torV3Config.enableTor || !torV3Config.enableHiddenService) { + printf("ERROR: Tor hidden service backend is disabled\n"); return false; } - // Use data directory for Tor service files - boost::filesystem::path serviceDir = GetDataDir() / "tor_data" / "triangles_v3"; - boost::filesystem::create_directories(serviceDir); + return AttachToBackendService(torV3Config.torDataDirectory, port); +} - // Create Tor configuration for hidden service - std::string torrcContent = strprintf( - "HiddenServiceDir %s\n" - "HiddenServiceVersion 3\n" - "HiddenServicePort %d 127.0.0.1:%d\n", - serviceDir.string().c_str(), port, port - ); - - // Write torrc file - std::ofstream torrcFile((serviceDir / "torrc").string().c_str()); - if (torrcFile.is_open()) { - torrcFile << torrcContent; - torrcFile.close(); +bool CTorV3Service::AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds) +{ + if (servicePort <= 0 || servicePort > 65535) { + printf("ERROR: Invalid backend hidden service port: %d\n", servicePort); + return false; } - // Write private key - std::ofstream keyFile((serviceDir / "hs_ed25519_secret_key").string().c_str()); - if (keyFile.is_open()) { - keyFile << "== ed25519v1-secret: type0 ==\n"; - keyFile << privateKey << "\n"; - keyFile.close(); + if (torDataDir.empty()) { + printf("ERROR: Tor data directory is empty\n"); + return false; } + port = servicePort; + + const boost::filesystem::path serviceDir = GetBackendHiddenServiceDir(torDataDir); + const boost::filesystem::path hostnamePath = serviceDir / "hostname"; + + std::string backendOnion; + for (int waited = 0; waited <= waitSeconds; ++waited) { + if (boost::filesystem::exists(hostnamePath) && + ReadTrimmedFirstLine(hostnamePath, backendOnion)) { + break; + } + + if (waited == waitSeconds) { + printf("ERROR: Timed out waiting for Tor hidden service hostname at %s\n", + hostnamePath.string().c_str()); + return false; + } + + if (fShutdown) { + printf("ERROR: Shutdown requested while waiting for Tor hidden service hostname\n"); + return false; + } + + MilliSleep(1000); + } + + if (!ValidateOnionAddress(backendOnion)) { + printf("ERROR: Tor backend produced invalid onion address: %s\n", backendOnion.c_str()); + return false; + } + + if (!onionAddress.empty() && onionAddress != backendOnion) { + printf("WARNING: Replacing wallet-managed onion address %s with Tor backend address %s\n", + onionAddress.c_str(), backendOnion.c_str()); + } + + onionAddress = backendOnion; isActive = true; - printf("Started V3 onion service on %s:%d\n", onionAddress.c_str(), port); + + if (pwalletMain) { + CWalletDB walletdb(pwalletMain->strWalletFile); + walletdb.WriteSetting("tor_v3_onion_address", onionAddress); + + // Back up the Tor-generated secret key to wallet.dat so the onion + // identity survives deletion of the tor_data directory. + boost::filesystem::path secretKeyPath = serviceDir / "hs_ed25519_secret_key"; + if (boost::filesystem::exists(secretKeyPath)) { + std::ifstream keyFile(secretKeyPath.string().c_str(), std::ios::binary); + if (keyFile.is_open()) { + std::vector keyData( + (std::istreambuf_iterator(keyFile)), + std::istreambuf_iterator()); + keyFile.close(); + + if (keyData.size() == 96) { + walletdb.WriteSetting("tor_v3_hs_secret_key_backup", keyData); + printf("Backed up Tor hidden service secret key to wallet (%d bytes)\n", + (int)keyData.size()); + } else { + printf("WARNING: hs_ed25519_secret_key has unexpected size %d (expected 96), not backing up\n", + (int)keyData.size()); + } + + OPENSSL_cleanse(keyData.data(), keyData.size()); + } + } + } + + printf("Attached V3 onion service to Tor backend at %s:%d\n", onionAddress.c_str(), port); return true; } @@ -1142,20 +1245,10 @@ void CTorV3Manager::ShutdownTor() bool CTorV3Manager::CreateWalletHiddenService(int port) { CTorV3Service* service = new CTorV3Service(); - - // Try to load existing service first - if (!service->LoadFromWallet()) { - // Generate new service - if (!service->GenerateV3Service(port)) { - delete service; - return false; - } - service->SaveToWallet(); - } - - if (service->StartOnionService()) { + + if (service->AttachToBackendService(torDataDir, port)) { services[port] = service; - printf("Wallet hidden service created: %s\n", service->GetOnionAddress().c_str()); + printf("Wallet hidden service attached: %s\n", service->GetOnionAddress().c_str()); // If seeder mode is enabled, automatically register as seeder if (torV3Config.enableSeederMode) { @@ -1968,12 +2061,12 @@ void CTorV3Manager::UpdateDiscoveryStats(int connected, int attempted) bool LoadTorV3Config() { // Tor V3 identity is innate to Triangles — enabled by default - torV3Config.enableTor = GetBoolArg("-tor", true); - torV3Config.enableHiddenService = GetBoolArg("-torhiddenservice", true); + torV3Config.enableTor = !GetBoolArg("-notor", false); + torV3Config.enableHiddenService = torV3Config.enableTor && GetBoolArg("-torhiddenservice", true); torV3Config.enableSeederMode = GetBoolArg("-torseeder", false); - torV3Config.hiddenServicePort = GetArg("-torhiddenserviceport", GetDefaultPort()); + torV3Config.hiddenServicePort = GetArg("-torhsport", GetListenPort()); torV3Config.torDataDirectory = GetArg("-tordatadir", (GetDataDir() / "tor_data").string()); - torV3Config.socksProxy = GetArg("-torproxy", "127.0.0.1:9050"); + torV3Config.socksProxy = GetEffectiveTorProxy(); torV3Config.maxConnections = GetArg("-tormaxconnections", 8); printf("Loaded Tor V3 configuration: enabled=%s, hidden_service=%s, seeder=%s, proxy=%s\n", @@ -2093,15 +2186,103 @@ void CTorV3Manager::RequestSeederListFromPeer(const std::string& peerAddress) // Schedule periodic seeder re-announcements void CTorV3Manager::ScheduleSeederReannouncement() { - // This would typically be handled by a timer or scheduler - // For now, we'll just update the last announcement time if (pwalletMain) { CWalletDB walletdb(pwalletMain->strWalletFile); walletdb.WriteSetting("seeder_last_announcement", (int64_t)GetTime()); - printf("Scheduled seeder re-announcement\n"); } } +// --------------------------------------------------------------------------- +// Background thread: Tor health monitoring + seeder maintenance +// --------------------------------------------------------------------------- +void ThreadTorMaintenance(void* parg) +{ + RenameThread("Triangles-tormaint"); + printf("Tor maintenance thread started\n"); + + int restartBackoffSec = 30; + int64_t lastSeederMaint = GetTime(); + static const int SEEDER_INTERVAL = 1800; // 30 minutes + + while (!fShutdown) + { + MilliSleep(30000); // check every 30 seconds + if (fShutdown) break; + + // --- Tor health check & auto-restart --- + if (!CTorEmbedded::GetInstance()->IsRunning()) + { + printf("WARNING: Tor process is no longer running, attempting restart...\n"); + + if (StartEmbeddedTor()) + { + printf("Tor restarted successfully\n"); + restartBackoffSec = 30; + + // Re-attach the hidden service identity + TorV3Config& torConfig = GetTorV3Config(); + std::string torDataPath = CTorEmbedded::GetInstance()->GetDataDir(); + if (torDataPath.empty()) + torDataPath = torConfig.torDataDirectory; + + torConfig.enableTor = true; + torConfig.enableHiddenService = CTorEmbedded::GetInstance()->IsHiddenServiceEnabled(); + torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort(); + torConfig.torDataDirectory = torDataPath; + + if (torConfig.enableHiddenService && InitTorV3()) + { + std::string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress(); + if (!onionAddr.empty()) + { + AddLocal(CService(onionAddr, torConfig.hiddenServicePort), LOCAL_MANUAL); + printf("Re-registered Tor V3 identity after restart: %s\n", onionAddr.c_str()); + } + } + } + else + { + printf("WARNING: Tor restart failed, retrying in %d seconds\n", restartBackoffSec); + MilliSleep(restartBackoffSec * 1000); + if (restartBackoffSec < 300) + restartBackoffSec *= 2; + } + continue; + } + + // --- Seeder maintenance (every 30 minutes) --- + TorV3Config& cfg = GetTorV3Config(); + if (cfg.enableSeederMode && (GetTime() - lastSeederMaint) >= SEEDER_INTERVAL) + { + CTorV3Manager* mgr = CTorV3Manager::GetInstance(); + std::string ownAddr = mgr->GetWalletOnionAddress(); + + if (!ownAddr.empty()) + { + // Re-announce ourselves as a seeder to all peers + { + LOCK(cs_vNodes); + for (CNode* pnode : vNodes) + { + try { + pnode->PushMessage("seeder", ownAddr, cfg.hiddenServicePort); + } catch (...) {} + } + } + printf("Seeder re-announcement sent to %d peers\n", (int)vNodes.size()); + } + + // Refresh our knowledge of other seeders + mgr->RequestSeederListFromPeers(); + + mgr->ScheduleSeederReannouncement(); + lastSeederMaint = GetTime(); + } + } + + printf("Tor maintenance thread exited\n"); +} + // Global functions bool InitTorV3() { @@ -2111,4 +2292,4 @@ bool InitTorV3() void ShutdownTorV3() { CTorV3Manager::GetInstance()->ShutdownTor(); -} \ No newline at end of file +} diff --git a/src/tor/onion_v3.h b/src/tor/onion_v3.h index 253a5bd..14ac5a0 100644 --- a/src/tor/onion_v3.h +++ b/src/tor/onion_v3.h @@ -34,7 +34,10 @@ public: // Start the onion service bool StartOnionService(); - + + // Attach to the hidden service managed by the running Tor backend + bool AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds = 30); + // Stop the onion service bool StopService(); @@ -169,5 +172,6 @@ void ShutdownTorV3(); TorV3Config& GetTorV3Config(); bool SaveTorV3Config(); bool LoadTorV3Config(); +void ThreadTorMaintenance(void* parg); -#endif // TRIANGLES_TOR_ONION_V3_H \ No newline at end of file +#endif // TRIANGLES_TOR_ONION_V3_H diff --git a/src/tor/tor_embedded.cpp b/src/tor/tor_embedded.cpp index 9823ba4..c334960 100644 --- a/src/tor/tor_embedded.cpp +++ b/src/tor/tor_embedded.cpp @@ -52,6 +52,7 @@ CTorEmbedded::CTorEmbedded() : running(false) , socksPort(19099) , hiddenServicePort(24112) + , hiddenServiceEnabled(true) { } @@ -109,20 +110,24 @@ static void TorThreadFunc(std::vector argv_strings) CTorEmbedded::GetInstance()->SetRunning(false); } -bool CTorEmbedded::Start(int socks, int hsPort) +bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService) { if (running.load()) return true; socksPort = socks; - hiddenServicePort = hsPort; + hiddenServiceEnabled = enableHiddenService; + hiddenServicePort = hiddenServiceEnabled ? hsPort : 0; + onionHostname.clear(); // Prepare Tor data directory under the wallet's data dir torDataDir = (::GetDataDir() / "tor_data").string(); fs::create_directories(torDataDir); - // Hidden service directory - std::string hsDir = (fs::path(torDataDir) / "hidden_service").string(); - fs::create_directories(hsDir); + std::string hsDir; + if (hiddenServiceEnabled) { + hsDir = (fs::path(torDataDir) / "hidden_service").string(); + fs::create_directories(hsDir); + } // Build the argv for tor_run_main std::vector argv; @@ -131,12 +136,14 @@ bool CTorEmbedded::Start(int socks, int hsPort) argv.push_back(std::to_string(socksPort)); argv.push_back("--DataDirectory"); argv.push_back(torDataDir); - argv.push_back("--HiddenServiceDir"); - argv.push_back(hsDir); - argv.push_back("--HiddenServiceVersion"); - argv.push_back("3"); - argv.push_back("--HiddenServicePort"); - argv.push_back(std::to_string(hiddenServicePort) + " 127.0.0.1:" + std::to_string(hiddenServicePort)); + if (hiddenServiceEnabled) { + argv.push_back("--HiddenServiceDir"); + argv.push_back(hsDir); + argv.push_back("--HiddenServiceVersion"); + argv.push_back("3"); + argv.push_back("--HiddenServicePort"); + argv.push_back(std::to_string(hiddenServicePort) + " 127.0.0.1:" + std::to_string(hiddenServicePort)); + } argv.push_back("--AvoidDiskWrites"); argv.push_back("1"); argv.push_back("--Log"); @@ -175,13 +182,15 @@ bool CTorEmbedded::Start(int socks, int hsPort) printf("Embedded Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1); // Read .onion hostname if available - fs::path hostnameFile = fs::path(hsDir) / "hostname"; - if (fs::exists(hostnameFile)) { - std::ifstream f(hostnameFile.string().c_str()); - if (f.is_open()) - std::getline(f, onionHostname); - if (!onionHostname.empty()) - printf("Tor hidden service: %s\n", onionHostname.c_str()); + if (hiddenServiceEnabled) { + fs::path hostnameFile = fs::path(hsDir) / "hostname"; + if (fs::exists(hostnameFile)) { + std::ifstream f(hostnameFile.string().c_str()); + if (f.is_open()) + std::getline(f, onionHostname); + if (!onionHostname.empty()) + printf("Tor hidden service: %s\n", onionHostname.c_str()); + } } return true; } @@ -219,14 +228,16 @@ void CTorEmbedded::Stop() #include "tor_process.h" -bool CTorEmbedded::Start(int socks, int hsPort) +bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService) { printf("Embedded Tor not compiled in. Using external Tor process.\n"); // Delegate to the external process manager socksPort = socks; - hiddenServicePort = hsPort; + hiddenServiceEnabled = enableHiddenService; + hiddenServicePort = hiddenServiceEnabled ? hsPort : 0; + onionHostname.clear(); torDataDir = (::GetDataDir() / "tor_data").string(); - running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort)); + running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort, hiddenServiceEnabled)); return running.load(); } @@ -249,10 +260,11 @@ bool StartEmbeddedTor() return false; } + bool enableHiddenService = GetBoolArg("-torhiddenservice", true); int socksPort = GetArg("-torsocks", 19099); - int hsPort = GetArg("-torhsport", GetListenPort()); + int hsPort = enableHiddenService ? GetArg("-torhsport", GetListenPort()) : 0; - return CTorEmbedded::GetInstance()->Start(socksPort, hsPort); + return CTorEmbedded::GetInstance()->Start(socksPort, hsPort, enableHiddenService); } void StopEmbeddedTor() diff --git a/src/tor/tor_embedded.h b/src/tor/tor_embedded.h index 116b055..18bd739 100644 --- a/src/tor/tor_embedded.h +++ b/src/tor/tor_embedded.h @@ -16,6 +16,7 @@ private: std::atomic running; int socksPort; int hiddenServicePort; + bool hiddenServiceEnabled; std::string torDataDir; std::string onionHostname; @@ -26,7 +27,7 @@ public: ~CTorEmbedded(); // Start Tor in a background thread (blocks that thread until shutdown) - bool Start(int socksPort = 19099, int hsPort = 24112); + bool Start(int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true); // Request Tor to shut down void Stop(); @@ -45,6 +46,7 @@ public: // Get the hidden service port int GetHiddenServicePort() const { return hiddenServicePort; } + bool IsHiddenServiceEnabled() const { return hiddenServiceEnabled; } }; // Global init/shutdown hooks (called from init.cpp) diff --git a/src/tor/tor_process.cpp b/src/tor/tor_process.cpp index e2845f1..5e7f2b9 100644 --- a/src/tor/tor_process.cpp +++ b/src/tor/tor_process.cpp @@ -48,6 +48,7 @@ CTorProcess* CTorProcess::GetInstance() CTorProcess::CTorProcess() : socksPort(19099) , hiddenServicePort(24112) + , hiddenServiceEnabled(true) , running(false) #ifdef WIN32 , hProcess(NULL) @@ -198,11 +199,13 @@ bool CTorProcess::WriteTorrc() fs::create_directories(torStateDir); torrc << "DataDirectory " << torStateDir.string() << "\n"; - // V3 hidden service so this node is reachable via .onion - torrc << "HiddenServiceDir " << hsDir.string() << "\n"; - torrc << "HiddenServiceVersion 3\n"; - torrc << "HiddenServicePort " << hiddenServicePort - << " 127.0.0.1:" << hiddenServicePort << "\n"; + if (hiddenServiceEnabled) { + // V3 hidden service so this node is reachable via .onion + torrc << "HiddenServiceDir " << hsDir.string() << "\n"; + torrc << "HiddenServiceVersion 3\n"; + torrc << "HiddenServicePort " << hiddenServicePort + << " 127.0.0.1:" << hiddenServicePort << "\n"; + } // Reduce bandwidth/resource usage for wallet use torrc << "ClientOnly 1\n"; @@ -213,15 +216,21 @@ bool CTorProcess::WriteTorrc() torrc.close(); - printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n", - torrcPath.c_str(), socksPort, hiddenServicePort); + if (hiddenServiceEnabled) { + printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n", + torrcPath.c_str(), socksPort, hiddenServicePort); + } else { + printf("Wrote torrc to %s (SOCKS %d, hidden service disabled)\n", + torrcPath.c_str(), socksPort); + } return true; } -bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort) +bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool enableHiddenService) { socksPort = socks; - hiddenServicePort = hsPort; + hiddenServiceEnabled = enableHiddenService; + hiddenServicePort = hiddenServiceEnabled ? hsPort : 0; torDataDir = dataDir; // Check if something is already listening on our SOCKS port @@ -314,12 +323,14 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort) printf("Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1); // Read and display the hidden service hostname if available - fs::path hsHostname = fs::path(torDataDir) / "hidden_service" / "hostname"; - if (fs::exists(hsHostname)) { - std::ifstream f(hsHostname.string().c_str()); - std::string hostname; - if (f.is_open() && std::getline(f, hostname)) { - printf("Tor hidden service: %s\n", hostname.c_str()); + if (hiddenServiceEnabled) { + fs::path hsHostname = fs::path(torDataDir) / "hidden_service" / "hostname"; + if (fs::exists(hsHostname)) { + std::ifstream f(hsHostname.string().c_str()); + std::string hostname; + if (f.is_open() && std::getline(f, hostname)) { + printf("Tor hidden service: %s\n", hostname.c_str()); + } } } return true; @@ -402,9 +413,9 @@ std::string CTorProcess::GetSocksProxy() const } // Global convenience functions -bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort) +bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort, bool enableHiddenService) { - return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort); + return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort, enableHiddenService); } void StopTorProcess() diff --git a/src/tor/tor_process.h b/src/tor/tor_process.h index 19cba09..f183d8f 100644 --- a/src/tor/tor_process.h +++ b/src/tor/tor_process.h @@ -21,6 +21,7 @@ private: std::string torrcPath; int socksPort; int hiddenServicePort; + bool hiddenServiceEnabled; bool running; #ifdef WIN32 @@ -45,7 +46,7 @@ public: // Start the Tor process // Returns true if Tor was started or is already running - bool Start(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112); + bool Start(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true); // Stop the Tor process void Stop(); @@ -64,7 +65,7 @@ public: }; // Global convenience functions -bool StartTorProcess(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112); +bool StartTorProcess(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true); void StopTorProcess(); #endif // TRIANGLES_TOR_PROCESS_H