43eaa96bc9
FastImportBlockFile wrote tx-index/UTXO/money-supply for EVERY block in blk0001.dat including orphaned side-chain blocks the file permanently retains. Those orphans outputs entered the UTXO set as phantom coins, inflating utxo_supply ~164k above true minted supply on every reindex. Fix: file-order pass only builds the block index; a second pass replays UTXO/supply along the active best-trust chain only. Also adds torrc.extra append hook for censored-network Tor.
607 lines
20 KiB
C++
607 lines
20 KiB
C++
// Copyright (c) 2024-2025 Triangles developers
|
|
// Tor Process Manager - launches and manages an external Tor binary
|
|
// Distributed under the MIT/X11 software license
|
|
|
|
#ifdef WIN32
|
|
#define NOMINMAX
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#ifndef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0600
|
|
#endif
|
|
#endif
|
|
|
|
#include "tor_process.h"
|
|
#include "../util.h"
|
|
#include "../net.h"
|
|
|
|
#include <boost/filesystem.hpp>
|
|
#include <fstream>
|
|
#include <cstdio>
|
|
#include <sstream>
|
|
|
|
#ifdef WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <tlhelp32.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
static std::string ReadTailLines(const fs::path& filePath, size_t maxLines)
|
|
{
|
|
std::ifstream in(filePath.string().c_str());
|
|
if (!in.is_open()) return "";
|
|
|
|
std::vector<std::string> lines;
|
|
std::string line;
|
|
while (std::getline(in, line)) {
|
|
lines.push_back(line);
|
|
if (lines.size() > maxLines) {
|
|
lines.erase(lines.begin());
|
|
}
|
|
}
|
|
|
|
std::ostringstream out;
|
|
for (size_t i = 0; i < lines.size(); ++i) {
|
|
if (i) out << " | ";
|
|
out << lines[i];
|
|
}
|
|
return out.str();
|
|
}
|
|
|
|
static CTorProcess* torProcessInstance = nullptr;
|
|
|
|
CTorProcess* CTorProcess::GetInstance()
|
|
{
|
|
if (!torProcessInstance) {
|
|
torProcessInstance = new CTorProcess();
|
|
}
|
|
return torProcessInstance;
|
|
}
|
|
|
|
CTorProcess::CTorProcess()
|
|
: socksPort(19099)
|
|
, hiddenServicePort(24112)
|
|
, hiddenServiceEnabled(true)
|
|
, running(false)
|
|
#ifdef WIN32
|
|
, hProcess(nullptr)
|
|
, hJob(nullptr)
|
|
, processId(0)
|
|
#else
|
|
, processId(0)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
CTorProcess::~CTorProcess()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
std::string CTorProcess::FindTorBinary()
|
|
{
|
|
// List of candidate paths to search for the Tor binary
|
|
std::vector<std::string> candidates;
|
|
|
|
#ifdef WIN32
|
|
// Same directory as the wallet executable
|
|
char exePath[MAX_PATH];
|
|
if (GetModuleFileNameA(nullptr, exePath, MAX_PATH)) {
|
|
fs::path exeDir = fs::path(exePath).parent_path();
|
|
candidates.push_back((exeDir / "tor.exe").string());
|
|
candidates.push_back((exeDir / "tor" / "tor.exe").string());
|
|
candidates.push_back((exeDir / "Tor" / "tor.exe").string());
|
|
}
|
|
|
|
// Data directory
|
|
candidates.push_back((GetDataDir() / "tor.exe").string());
|
|
candidates.push_back((GetDataDir() / "tor" / "tor.exe").string());
|
|
|
|
// Common Windows install locations
|
|
const char* programFiles = getenv("ProgramFiles");
|
|
if (programFiles) {
|
|
candidates.push_back(std::string(programFiles) + "\\Tor\\tor.exe");
|
|
candidates.push_back(std::string(programFiles) + "\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe");
|
|
}
|
|
const char* programFilesX86 = getenv("ProgramFiles(x86)");
|
|
if (programFilesX86) {
|
|
candidates.push_back(std::string(programFilesX86) + "\\Tor\\tor.exe");
|
|
candidates.push_back(std::string(programFilesX86) + "\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe");
|
|
}
|
|
const char* localAppData = getenv("LOCALAPPDATA");
|
|
if (localAppData) {
|
|
candidates.push_back(std::string(localAppData) + "\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe");
|
|
}
|
|
|
|
// Tor Expert Bundle (common install)
|
|
candidates.push_back("C:\\Tor\\tor.exe");
|
|
|
|
#else
|
|
// Same directory as the wallet executable
|
|
char exePath[4096];
|
|
ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
|
|
if (len > 0) {
|
|
exePath[len] = '\0';
|
|
fs::path exeDir = fs::path(exePath).parent_path();
|
|
candidates.push_back((exeDir / "tor").string());
|
|
candidates.push_back((exeDir / "tor" / "tor").string());
|
|
}
|
|
|
|
// Data directory
|
|
candidates.push_back((GetDataDir() / "tor").string());
|
|
candidates.push_back((GetDataDir() / "tor" / "tor").string());
|
|
|
|
// Standard Linux/macOS locations
|
|
candidates.push_back("/usr/bin/tor");
|
|
candidates.push_back("/usr/local/bin/tor");
|
|
candidates.push_back("/usr/sbin/tor");
|
|
candidates.push_back("/opt/tor/bin/tor");
|
|
candidates.push_back("/snap/bin/tor");
|
|
|
|
// Homebrew (macOS)
|
|
candidates.push_back("/opt/homebrew/bin/tor");
|
|
candidates.push_back("/usr/local/opt/tor/bin/tor");
|
|
#endif
|
|
|
|
// Check each candidate (must be a regular file, not a directory)
|
|
for (const std::string& path : candidates) {
|
|
if (fs::exists(path) && !fs::is_directory(path)) {
|
|
printf("Found Tor binary at: %s\n", path.c_str());
|
|
return path;
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool CTorProcess::IsPortInUse(int port)
|
|
{
|
|
#ifdef WIN32
|
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sock == INVALID_SOCKET) return false;
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
|
addr.sin_port = htons(port);
|
|
|
|
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
|
closesocket(sock);
|
|
return (result == 0);
|
|
#else
|
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock < 0) return false;
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
|
addr.sin_port = htons(port);
|
|
|
|
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
|
close(sock);
|
|
return (result == 0);
|
|
#endif
|
|
}
|
|
|
|
#ifdef WIN32
|
|
bool CTorProcess::KillOrphanedTor()
|
|
{
|
|
// Walk all processes looking for tor.exe listening on our SOCKS port.
|
|
// We identify orphans by matching the executable name AND checking that
|
|
// the Tor data directory inside our wallet data dir has a matching PID lock.
|
|
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (hSnap == INVALID_HANDLE_VALUE) return false;
|
|
|
|
PROCESSENTRY32 pe;
|
|
pe.dwSize = sizeof(pe);
|
|
bool killed = false;
|
|
|
|
if (Process32First(hSnap, &pe)) {
|
|
do {
|
|
// Case-insensitive compare against "tor.exe"
|
|
if (_stricmp(pe.szExeFile, "tor.exe") != 0)
|
|
continue;
|
|
|
|
printf("Found orphaned tor.exe (PID %lu), terminating...\n", pe.th32ProcessID);
|
|
HANDLE h = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, pe.th32ProcessID);
|
|
if (h) {
|
|
TerminateProcess(h, 0);
|
|
WaitForSingleObject(h, 5000);
|
|
CloseHandle(h);
|
|
killed = true;
|
|
}
|
|
} while (Process32Next(hSnap, &pe));
|
|
}
|
|
|
|
CloseHandle(hSnap);
|
|
|
|
if (killed) {
|
|
// Give the OS a moment to release the port
|
|
MilliSleep(1000);
|
|
}
|
|
return killed;
|
|
}
|
|
#endif
|
|
|
|
bool CTorProcess::WriteTorrc()
|
|
{
|
|
fs::path dataPath(torDataDir);
|
|
fs::create_directories(dataPath);
|
|
|
|
torrcPath = (dataPath / "torrc").string();
|
|
|
|
// Hidden service directory
|
|
fs::path hsDir = dataPath / "hidden_service";
|
|
fs::create_directories(hsDir);
|
|
|
|
std::ofstream torrc(torrcPath.c_str());
|
|
if (!torrc.is_open()) {
|
|
printf("ERROR: Cannot write torrc to %s\n", torrcPath.c_str());
|
|
return false;
|
|
}
|
|
|
|
torrc << "# Triangles Wallet Tor Configuration (auto-generated)\n";
|
|
torrc << "# Do not edit - this file is overwritten on startup\n\n";
|
|
|
|
// SOCKS proxy for wallet connections
|
|
torrc << "SocksPort " << socksPort << "\n";
|
|
|
|
// Data directory for Tor state.
|
|
// Use the tor_data directory itself as DataDirectory so that Tor creates
|
|
// its internal 'state' FILE at <tor_data>/state. Older wallet builds
|
|
// erroneously created a subdirectory called 'state' and pointed
|
|
// DataDirectory at it; newer Tor versions (0.4.9+) reject that because
|
|
// they expect to write a plain file called 'state' inside DataDirectory.
|
|
//
|
|
// Recovery: if 'state' exists as a directory, move its contents up and
|
|
// remove it so that Tor can create its state file in the normal location.
|
|
{
|
|
fs::path badStateDir = dataPath / "state";
|
|
if (fs::exists(badStateDir) && fs::is_directory(badStateDir)) {
|
|
// Migrate any files inside the bad 'state/' directory up to dataPath
|
|
try {
|
|
for (auto& entry : fs::directory_iterator(badStateDir)) {
|
|
fs::path dest = dataPath / entry.path().filename();
|
|
if (!fs::exists(dest)) {
|
|
fs::rename(entry.path(), dest);
|
|
}
|
|
}
|
|
fs::remove(badStateDir);
|
|
printf("Auto-recovered: removed legacy 'state' directory from %s\n", dataPath.string().c_str());
|
|
} catch (const fs::filesystem_error& e) {
|
|
printf("WARNING: Could not auto-recover tor_data/state directory: %s\n", e.what());
|
|
}
|
|
}
|
|
}
|
|
torrc << "DataDirectory " << dataPath.string() << "\n";
|
|
|
|
// Persistent Tor log for post-mortem debugging on user machines.
|
|
fs::path torLogPath = dataPath / "tor.log";
|
|
torrc << "Log notice file " << torLogPath.string() << "\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";
|
|
}
|
|
|
|
// Only force client-only mode when we are NOT publishing a hidden service.
|
|
// When HiddenServicePort is configured, Tor needs to behave as a hidden
|
|
// service host, not just as a pure outbound client proxy.
|
|
if (!hiddenServiceEnabled) {
|
|
torrc << "ClientOnly 1\n";
|
|
}
|
|
|
|
// Disable unused features
|
|
torrc << "AvoidDiskWrites 1\n";
|
|
torrc << "Log notice stderr\n";
|
|
|
|
// Append user-supplied extra Tor configuration if present. This lets
|
|
// operators on censored / DPI-filtered networks add Bridge lines,
|
|
// ClientTransportPlugin (obfs4), or a Socks5Proxy/HTTPSProxy upstream so
|
|
// Tor can reach the network when direct connections are blocked. The file
|
|
// is never overwritten by the wallet; only the auto-generated torrc is.
|
|
{
|
|
fs::path extraPath = dataPath / "torrc.extra";
|
|
if (fs::exists(extraPath)) {
|
|
std::ifstream extra(extraPath.string().c_str());
|
|
if (extra.is_open()) {
|
|
torrc << "\n# ---- appended from torrc.extra (user-managed) ----\n";
|
|
torrc << extra.rdbuf();
|
|
torrc << "\n";
|
|
printf("Tor: appended user configuration from %s\n", extraPath.string().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
torrc.close();
|
|
|
|
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 enableHiddenService)
|
|
{
|
|
lastError.clear();
|
|
socksPort = socks;
|
|
hiddenServiceEnabled = enableHiddenService;
|
|
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
|
torDataDir = dataDir;
|
|
|
|
// Check if something is already listening on our SOCKS port
|
|
if (IsPortInUse(socksPort)) {
|
|
#ifdef WIN32
|
|
// An orphaned tor.exe from a previous wallet session is likely still
|
|
// running. Kill it so we can start a fresh one under our Job Object.
|
|
printf("Tor SOCKS port %d already in use - killing orphaned tor.exe\n", socksPort);
|
|
KillOrphanedTor();
|
|
// If the port is STILL in use after killing all tor.exe, something
|
|
// else owns it. Fall through and let the new Tor fail gracefully
|
|
// rather than silently adopting an unknown process.
|
|
if (IsPortInUse(socksPort)) {
|
|
printf("WARNING: Port %d still in use after killing tor.exe - another process owns it\n", socksPort);
|
|
}
|
|
#else
|
|
// On Linux the child is reaped via waitpid, so orphans are less common.
|
|
// If the port is busy, assume a system Tor or leftover process.
|
|
printf("Tor SOCKS port %d already in use - killing orphaned tor\n", socksPort);
|
|
// Try to find and kill by PID file
|
|
fs::path pidFile = fs::path(torDataDir) / "state" / "pid";
|
|
if (fs::exists(pidFile)) {
|
|
std::ifstream f(pidFile.string().c_str());
|
|
pid_t oldPid = 0;
|
|
if (f >> oldPid && oldPid > 0) {
|
|
printf("Found stale Tor PID %d, sending SIGTERM...\n", oldPid);
|
|
kill(oldPid, SIGTERM);
|
|
for (int i = 0; i < 30; i++) {
|
|
MilliSleep(100);
|
|
if (kill(oldPid, 0) != 0) break;
|
|
}
|
|
if (kill(oldPid, 0) == 0) {
|
|
printf("Tor PID %d still alive, sending SIGKILL...\n", oldPid);
|
|
kill(oldPid, SIGKILL);
|
|
MilliSleep(500);
|
|
}
|
|
}
|
|
}
|
|
if (IsPortInUse(socksPort)) {
|
|
printf("WARNING: Port %d still in use after cleanup - another process owns it\n", socksPort);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Find Tor binary
|
|
torBinaryPath = FindTorBinary();
|
|
if (torBinaryPath.empty()) {
|
|
lastError = "Tor binary was not found in the bundled install or standard search paths.";
|
|
printf("WARNING: Tor binary not found. Install Tor for .onion connectivity.\n");
|
|
printf(" Windows: Download from https://www.torproject.org/download/tor/\n");
|
|
printf(" Linux: apt install tor or yum install tor\n");
|
|
printf(" Place tor executable next to the wallet binary for auto-detection.\n");
|
|
return false;
|
|
}
|
|
|
|
// Write configuration
|
|
if (!WriteTorrc()) {
|
|
lastError = strprintf("Failed to write Tor configuration to %s", torrcPath.c_str());
|
|
printf("ERROR: Failed to write Tor configuration\n");
|
|
return false;
|
|
}
|
|
|
|
printf("Starting Tor process: %s -f %s\n", torBinaryPath.c_str(), torrcPath.c_str());
|
|
|
|
#ifdef WIN32
|
|
STARTUPINFOA si;
|
|
PROCESS_INFORMATION pi;
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
|
si.wShowWindow = SW_HIDE; // Run hidden
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
|
|
std::string cmdLine = "\"" + torBinaryPath + "\" -f \"" + torrcPath + "\"";
|
|
|
|
if (!CreateProcessA(
|
|
nullptr,
|
|
(LPSTR)cmdLine.c_str(),
|
|
nullptr, nullptr,
|
|
FALSE,
|
|
CREATE_NO_WINDOW,
|
|
nullptr, nullptr,
|
|
&si, &pi))
|
|
{
|
|
DWORD err = ::GetLastError();
|
|
lastError = strprintf("CreateProcess failed for Tor binary '%s' with Windows error %lu", torBinaryPath.c_str(), err);
|
|
printf("ERROR: Failed to start Tor process (error %lu)\n", err);
|
|
return false;
|
|
}
|
|
|
|
hProcess = pi.hProcess;
|
|
processId = pi.dwProcessId;
|
|
CloseHandle(pi.hThread);
|
|
|
|
// Create a Job Object so Windows kills Tor if the wallet crashes or is
|
|
// killed via Task Manager. JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE means
|
|
// all processes in the job die when the last handle to the job closes
|
|
// (i.e. when our process exits for any reason).
|
|
hJob = CreateJobObject(nullptr, nullptr);
|
|
if (hJob) {
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
|
|
jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation,
|
|
&jobInfo, sizeof(jobInfo));
|
|
if (!AssignProcessToJobObject(hJob, hProcess)) {
|
|
printf("WARNING: Could not assign Tor to Job Object (error %lu)\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
printf("Tor process started (PID %lu)\n", processId);
|
|
#else
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
printf("ERROR: Failed to fork for Tor process\n");
|
|
return false;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
// Child process - exec Tor
|
|
// Redirect stdout/stderr to /dev/null to avoid cluttering wallet output
|
|
freopen("/dev/null", "w", stdout);
|
|
freopen("/dev/null", "w", stderr);
|
|
execl(torBinaryPath.c_str(), torBinaryPath.c_str(),
|
|
"-f", torrcPath.c_str(), (char*)nullptr);
|
|
// If exec fails, exit child
|
|
_exit(1);
|
|
}
|
|
|
|
processId = pid;
|
|
printf("Tor process started (PID %d)\n", processId);
|
|
#endif
|
|
|
|
running = true;
|
|
|
|
// Wait a few seconds for Tor to bootstrap, then check if SOCKS is up
|
|
printf("Waiting for Tor to bootstrap...\n");
|
|
for (int i = 0; i < 30; i++) {
|
|
MilliSleep(1000);
|
|
if (fShutdown) {
|
|
Stop();
|
|
return false;
|
|
}
|
|
if (IsPortInUse(socksPort)) {
|
|
printf("Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1);
|
|
|
|
// Read and display the hidden service hostname if available
|
|
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;
|
|
}
|
|
|
|
// Check if Tor process is still alive
|
|
if (!IsRunning()) {
|
|
fs::path torLogPath = fs::path(torDataDir) / "tor.log";
|
|
std::string torLogTail = ReadTailLines(torLogPath, 8);
|
|
if (!torLogTail.empty()) {
|
|
lastError = strprintf("Tor process exited during bootstrap before the SOCKS port became ready. Recent tor.log: %s", torLogTail.c_str());
|
|
} else {
|
|
lastError = "Tor process exited during bootstrap before the SOCKS port became ready.";
|
|
}
|
|
printf("ERROR: Tor process exited prematurely\n");
|
|
running = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
lastError = strprintf("Tor process started from '%s' but SOCKS port %d was not ready after 30 seconds.", torBinaryPath.c_str(), socksPort);
|
|
printf("WARNING: Tor started but SOCKS proxy not yet ready after 30s\n");
|
|
printf(" Tor may still be bootstrapping. .onion connections will work once ready.\n");
|
|
return true;
|
|
}
|
|
|
|
void CTorProcess::Stop()
|
|
{
|
|
if (!running) return;
|
|
|
|
#ifdef WIN32
|
|
if (hProcess != nullptr) {
|
|
printf("Stopping Tor process (PID %lu)...\n", processId);
|
|
TerminateProcess(hProcess, 0);
|
|
WaitForSingleObject(hProcess, 5000);
|
|
CloseHandle(hProcess);
|
|
hProcess = nullptr;
|
|
}
|
|
if (hJob != nullptr) {
|
|
CloseHandle(hJob);
|
|
hJob = nullptr;
|
|
}
|
|
#else
|
|
if (processId > 0) {
|
|
printf("Stopping Tor process (PID %d)...\n", processId);
|
|
kill(processId, SIGTERM);
|
|
// Wait up to 5 seconds for graceful shutdown
|
|
for (int i = 0; i < 50; i++) {
|
|
int status;
|
|
pid_t result = waitpid(processId, &status, WNOHANG);
|
|
if (result != 0) break;
|
|
MilliSleep(100);
|
|
}
|
|
// Force kill if still running
|
|
kill(processId, SIGKILL);
|
|
waitpid(processId, nullptr, 0);
|
|
}
|
|
#endif
|
|
|
|
processId = 0;
|
|
running = false;
|
|
printf("Tor process stopped\n");
|
|
}
|
|
|
|
bool CTorProcess::IsRunning()
|
|
{
|
|
if (!running) return false;
|
|
|
|
#ifdef WIN32
|
|
if (hProcess == nullptr) return false;
|
|
DWORD exitCode;
|
|
if (GetExitCodeProcess(hProcess, &exitCode)) {
|
|
return (exitCode == STILL_ACTIVE);
|
|
}
|
|
return false;
|
|
#else
|
|
if (processId <= 0) return false;
|
|
int status;
|
|
pid_t result = waitpid(processId, &status, WNOHANG);
|
|
if (result == 0) return true; // Still running
|
|
if (result == processId) {
|
|
running = false;
|
|
return false; // Exited
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
std::string CTorProcess::GetSocksProxy() const
|
|
{
|
|
return "127.0.0.1:" + std::to_string(socksPort);
|
|
}
|
|
|
|
// Global convenience functions
|
|
bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort, bool enableHiddenService)
|
|
{
|
|
return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort, enableHiddenService);
|
|
}
|
|
|
|
void StopTorProcess()
|
|
{
|
|
CTorProcess::GetInstance()->Stop();
|
|
}
|