Files
triangles_v5/src/tor/tor_embedded.cpp
T
sami7777 cd51ba41d8 Remove stale AI-generated documentation
Drop seven planning/strategy/upgrade-notes docs that have outlived their
usefulness, plus the dangling CODEX-TOR-GUIDE.md reference in
tor_embedded.cpp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:16:32 -07:00

285 lines
8.7 KiB
C++

// Copyright (c) 2025-2026 Triangles developers
// Embedded Tor integration - runs Tor in-process as a library
// Distributed under the MIT/X11 software license
//
// BUILD REQUIREMENT: Link against libtor.a built from the official Tor source.
//
// This file compiles in two modes:
// 1. ENABLE_TOR_EMBEDDED defined: full embedded Tor via tor_api.h
// 2. ENABLE_TOR_EMBEDDED not defined: stubs that fall back to external tor_process
#include "tor_embedded.h"
#include "../util.h"
#include "../net.h"
#include <boost/filesystem.hpp>
#include <boost/thread/thread.hpp>
#include <fstream>
#include <cstring>
#include <vector>
#include <string>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
#ifdef ENABLE_TOR_EMBEDDED
// Official Tor C API (tor >= 0.4.5)
extern "C" {
#include <tor_api.h>
}
#endif
namespace fs = boost::filesystem;
// Singleton
CTorEmbedded* CTorEmbedded::instance = nullptr;
CTorEmbedded* CTorEmbedded::GetInstance()
{
if (!instance)
instance = new CTorEmbedded();
return instance;
}
CTorEmbedded::CTorEmbedded()
: running(false)
, socksPort(19099)
, hiddenServicePort(24112)
, hiddenServiceEnabled(true)
{
}
CTorEmbedded::~CTorEmbedded()
{
Stop();
}
std::string CTorEmbedded::GetSocksProxy() const
{
return "127.0.0.1:" + std::to_string(socksPort);
}
#ifdef ENABLE_TOR_EMBEDDED
// ========================================================================
// Embedded mode: Tor runs in-process via libtor.a / tor_api.h
// ========================================================================
// The Tor thread entry point. tor_run_main() blocks until Tor shuts down.
static void TorThreadFunc(std::vector<std::string> argv_strings)
{
// Build a C argv array that tor_run_main expects.
std::vector<char*> argv_ptrs;
for (auto& s : argv_strings)
argv_ptrs.push_back(&s[0]);
argv_ptrs.push_back(nullptr);
tor_main_configuration_t* cfg = tor_main_configuration_new();
if (!cfg) {
printf("ERROR: tor_main_configuration_new() failed\n");
CTorEmbedded::GetInstance()->SetRunning(false);
return;
}
// argc does not count the trailing nullptr
int rc = tor_main_configuration_set_command_line(
cfg, (int)(argv_ptrs.size() - 1), argv_ptrs.data());
if (rc != 0) {
printf("ERROR: tor_main_configuration_set_command_line() returned %d\n", rc);
tor_main_configuration_free(cfg);
CTorEmbedded::GetInstance()->SetRunning(false);
return;
}
printf("Embedded Tor starting (SOCKS %d, HS port %d)...\n",
CTorEmbedded::GetInstance()->GetSocksPort(),
CTorEmbedded::GetInstance()->GetHiddenServicePort());
// This blocks until Tor exits
rc = tor_run_main(cfg);
tor_main_configuration_free(cfg);
printf("Embedded Tor exited with code %d\n", rc);
CTorEmbedded::GetInstance()->SetRunning(false);
}
bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
{
if (running.load()) return true;
lastError.clear();
socksPort = socks;
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);
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<std::string> argv;
argv.push_back("tor"); // program name (argv[0])
argv.push_back("--SocksPort");
argv.push_back(std::to_string(socksPort));
argv.push_back("--DataDirectory");
argv.push_back(torDataDir);
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");
argv.push_back("notice stderr");
running.store(true);
// Launch Tor on a dedicated thread (tor_run_main blocks)
boost::thread torThread(TorThreadFunc, argv);
torThread.detach();
// Wait for SOCKS port to become available (up to 60s)
printf("Waiting for embedded Tor to bootstrap...\n");
for (int i = 0; i < 60; i++) {
MilliSleep(1000);
if (fShutdown) {
Stop();
return false;
}
// Quick port check
#ifdef WIN32
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock != INVALID_SOCKET) {
#else
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock >= 0) {
#endif
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(socksPort);
bool up = (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0);
#ifdef WIN32
closesocket(sock);
#else
close(sock);
#endif
if (up) {
printf("Embedded Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1);
// Read .onion hostname if available
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;
}
}
if (!running.load()) {
lastError = "Embedded Tor thread exited during bootstrap before the SOCKS proxy became available.";
printf("ERROR: Embedded Tor thread exited during bootstrap\n");
return false;
}
}
lastError = strprintf("Embedded Tor did not expose SOCKS port %d within 60 seconds.", socksPort);
printf("WARNING: Embedded Tor started but SOCKS not ready after 60s (still bootstrapping)\n");
return true;
}
void CTorEmbedded::Stop()
{
if (!running.load()) return;
printf("Requesting embedded Tor shutdown...\n");
// tor_run_main respects signals; raise SIGTERM to trigger graceful exit
#ifndef WIN32
// On Unix we can signal our own process; the Tor thread handles it
// Actually, tor_api doesn't provide a clean shutdown function in 0.4.x
// For now, the thread will exit when the process exits.
// TODO: Tor 0.4.9+ may add tor_api_shutdown(), use it when available
#endif
running.store(false);
}
#else // !ENABLE_TOR_EMBEDDED
// ========================================================================
// Fallback stubs: embedded Tor not compiled in, use external tor_process
// ========================================================================
#include "tor_process.h"
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;
hiddenServiceEnabled = enableHiddenService;
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
onionHostname.clear();
lastError.clear();
torDataDir = (::GetDataDir() / "tor_data").string();
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort, hiddenServiceEnabled));
if (!running.load()) {
lastError = CTorProcess::GetInstance()->GetStartupError();
}
return running.load();
}
void CTorEmbedded::Stop()
{
StopTorProcess();
running.store(false);
}
#endif // ENABLE_TOR_EMBEDDED
// ========================================================================
// Global hooks (called from init.cpp)
// ========================================================================
bool StartEmbeddedTor()
{
if (GetBoolArg("-notor", false)) {
printf("Tor disabled by -notor flag\n");
return false;
}
bool enableHiddenService = GetBoolArg("-torhiddenservice", true);
int socksPort = GetArg("-torsocks", 19099);
int hsPort = enableHiddenService ? GetArg("-torhsport", GetListenPort()) : 0;
return CTorEmbedded::GetInstance()->Start(socksPort, hsPort, enableHiddenService);
}
void StopEmbeddedTor()
{
CTorEmbedded::GetInstance()->Stop();
}