Skip to content

Commit

Permalink
Merge 15421 via tor_subprocess-28+knots
Browse files Browse the repository at this point in the history
  • Loading branch information
luke-jr committed Feb 14, 2025
2 parents b7af4e6 + 57a23f5 commit e1240bc
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 13 deletions.
13 changes: 13 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ AC_ARG_ENABLE([external-signer],
[use_external_signer=$enableval],
[use_external_signer=yes])

AC_ARG_ENABLE([tor-subprocess],
[AS_HELP_STRING([--disable-tor-subprocess],[compile support for executing a dedicated Tor node (default is yes)])],
[use_tor_subprocess=$enableval],
[use_tor_subprocess=yes])

AC_LANG_PUSH([C++])

dnl Always set -g -O2 in our CXXFLAGS. Autoconf will try and set CXXFLAGS to "-g -O2" by default,
Expand Down Expand Up @@ -1327,6 +1332,7 @@ if test "$enable_fuzz" = "yes"; then
bitcoin_enable_qt_dbus=no
use_bench=no
use_tests=no
use_tor_subprocess=no
use_external_signer=no
use_upnp=no
use_natpmp=no
Expand Down Expand Up @@ -1537,6 +1543,11 @@ if test "$use_external_signer" = "yes"; then
fi
AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "$use_external_signer" = "yes"])

if test "$use_tor_subprocess" = "yes"; then
AC_DEFINE([ENABLE_TOR_SUBPROCESS], [1], [Define if Tor subprocess support is enabled])
fi
AM_CONDITIONAL([ENABLE_TOR_SUBPROCESS], [test "$use_tor_subprocess" = "yes"])

dnl Check for reduced exports
if test "$use_reduce_exports" = "yes"; then
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [CORE_CXXFLAGS="$CORE_CXXFLAGS -fvisibility=hidden"],
Expand Down Expand Up @@ -1911,6 +1922,7 @@ AC_SUBST(LIBTOOL_APP_LDFLAGS)
AC_SUBST(USE_SQLITE)
AC_SUBST(USE_BDB)
AC_SUBST(ENABLE_EXTERNAL_SIGNER)
AC_SUBST(ENABLE_TOR_SUBPROCESS)
AC_SUBST(USE_UPNP)
AC_SUBST(USE_QRCODE)
AC_SUBST(TESTDEFS)
Expand Down Expand Up @@ -1979,6 +1991,7 @@ esac
echo
echo "Options used to compile and link:"
echo " external signer = $use_external_signer"
echo " tor subprocess = $use_tor_subprocess"
echo " multiprocess = $build_multiprocess"
echo " with wallet = $enable_wallet"
if test "$enable_wallet" != "no"; then
Expand Down
5 changes: 5 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,11 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peertimeout=<n>", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control host and port to use if onion listening enabled (default: %s). If no port is specified, the default port of %i will be used.", DEFAULT_TOR_CONTROL, DEFAULT_TOR_CONTROL_PORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#ifdef HAVE_TOR_SUBPROCESS
argsman.AddArg("-torexecute=<command>", strprintf("Tor command to use if not already running (default: %s)", DEFAULT_TOR_EXECUTE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#else
hidden_args.emplace_back("-torexecute=<command>");
#endif
argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);
#ifdef USE_UPNP
argsman.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", DEFAULT_UPNP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
Expand Down
3 changes: 2 additions & 1 deletion src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ bool AddLocal(const CService& addr_, int nScore)
if (!fDiscover && nScore < LOCAL_MANUAL)
return false;

if (!g_reachable_nets.Contains(addr))
// IPv4 and IPv6 cannot be connected to unless their networks are reachable, but Tor is not necessarily bidirectional
if (!(g_reachable_nets.Contains(addr) || addr.IsTor()))
return false;

LogPrintf("AddLocal(%s,%i)\n", addr.ToStringAddrPort(), nScore);
Expand Down
133 changes: 124 additions & 9 deletions src/torcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif

#include <torcontrol.h>

#include <chainparams.h>
Expand All @@ -24,10 +28,16 @@
#include <util/thread.h>
#include <util/time.h>

#ifdef ENABLE_TOR_SUBPROCESS
#include <util/subprocess.h>
#endif // ENABLE_TOR_SUBPROCESS

#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdlib>
#include <deque>
#include <fstream>
#include <functional>
#include <map>
#include <optional>
Expand All @@ -48,6 +58,7 @@ using util::ToString;

/** Default control ip and port */
const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:" + ToString(DEFAULT_TOR_CONTROL_PORT);
const std::string DEFAULT_TOR_EXECUTE = "tor";
/** Tor cookie size (from control-spec.txt) */
static const int TOR_COOKIE_SIZE = 32;
/** Size of client/server nonce for SAFECOOKIE */
Expand Down Expand Up @@ -80,6 +91,10 @@ TorControlConnection::~TorControlConnection()
bufferevent_free(b_conn);
}

void TorControlConnection::IgnoreReplyHandler(TorControlConnection &a, const TorControlReply &b)
{
}

void TorControlConnection::readcb(struct bufferevent *bev, void *ctx)
{
TorControlConnection *self = static_cast<TorControlConnection*>(ctx);
Expand Down Expand Up @@ -318,18 +333,20 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s)
return mapping;
}

TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target):
TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target, const std::string& execute):
base(_base),
m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_timeout(RECONNECT_TIMEOUT_START),
m_connect_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_timeout(RECONNECT_TIMEOUT_START),
m_execute(execute),
m_target(target)
{
reconnect_ev = event_new(base, -1, 0, reconnect_cb, this);
if (!reconnect_ev)
LogPrintf("tor: Failed to create event for reconnection: out of memory?\n");
// Start connection attempts immediately
if (!conn.Connect(m_tor_control_center, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
m_current_tor_control_center = tor_control_center;
if (!conn.Connect(m_current_tor_control_center, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) {
LogPrintf("tor: Initiating connection to Tor control port %s failed\n", m_tor_control_center);
LogPrintf("tor: Initiating connection to Tor control port %s failed\n", m_current_tor_control_center);
}
// Read service private key if cached
std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile());
Expand All @@ -348,6 +365,12 @@ TorController::~TorController()
if (service.IsValid()) {
RemoveLocal(service);
}
#ifdef ENABLE_TOR_SUBPROCESS
if (m_process) {
conn.Command("SIGNAL SHUTDOWN");
delete m_process;
}
#endif
}

void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlReply& reply)
Expand Down Expand Up @@ -457,9 +480,20 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
if (reply.code == 250) {
LogPrint(BCLog::TOR, "Authentication successful\n");

#ifdef ENABLE_TOR_SUBPROCESS
if (m_process) {
_conn.Command("TAKEOWNERSHIP");
}
#endif

// Now that we know Tor is running setup the proxy for onion addresses
// if -onion isn't set to something else.
if (gArgs.GetArg("-onion", "") == "") {
// NOTE: Our own private Tor doesn't do SOCKS, so don't configure it
if (gArgs.GetArg("-onion", "") == ""
#ifdef ENABLE_TOR_SUBPROCESS
&& !m_process
#endif
) {
_conn.Command("GETINFO net/listeners/socks", std::bind(&TorController::get_socks_cb, this, std::placeholders::_1, std::placeholders::_2));
}

Expand Down Expand Up @@ -616,22 +650,93 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro

void TorController::connected_cb(TorControlConnection& _conn)
{
m_try_exec = false;
reconnect_timeout = RECONNECT_TIMEOUT_START;
// First send a PROTOCOLINFO command to figure out what authentication is expected
if (!_conn.Command("PROTOCOLINFO 1", std::bind(&TorController::protocolinfo_cb, this, std::placeholders::_1, std::placeholders::_2)))
LogPrintf("tor: Error sending initial protocolinfo command\n");
}

std::string TorController::LaunchTor()
{
#ifdef ENABLE_TOR_SUBPROCESS
fs::path tor_datadir = gArgs.GetDataDirNet() / "tor";
const fs::path controlport_env_filepath = tor_datadir / "controlport.env";
fs::remove(controlport_env_filepath); // may throw exceptions

if (m_process) {
try {
m_process->kill();
} catch (...) {
// ignore any exceptions
}
delete m_process;
m_process = nullptr;
}

try {
m_process = new subprocess::Popen(m_execute + " -f -", subprocess::input{subprocess::PIPE}, subprocess::close_fds{true});
} catch (...) {
LogPrint(BCLog::TOR, "tor: Failed to execute Tor process\n");
throw;
}
m_process->send(std::string{"SOCKSPort 0\n"});
m_process->send(std::string{"DataDirectory "} + fs::PathToString(tor_datadir) + "\n");
m_process->send(std::string{"ControlPort auto\n"});
m_process->send(std::string{"ControlPortWriteToFile "} + fs::PathToString(controlport_env_filepath) + "\n");
m_process->send(std::string{"CookieAuthentication 1\n"});

while (!fs::exists(controlport_env_filepath)) {
if (m_process->poll() != -1) {
LogPrint(BCLog::TOR, "tor: Tor process died before making control port file\n");
throw std::runtime_error("tor process died");
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}

std::ifstream controlport_file(controlport_env_filepath);
std::string portline;
controlport_file >> portline;
if (portline.compare(0, 5, "PORT=")) {
LogPrint(BCLog::TOR, "tor: Unrecognized control port line in file\n");
m_process->kill();
delete m_process;
m_process = nullptr;
throw std::runtime_error("port line unrecognized");
}

return portline.substr(5);
#else
throw std::runtime_error("not supported");
#endif
}

void TorController::disconnected_cb(TorControlConnection& _conn)
{
// Stop advertising service when disconnected
if (service.IsValid())
RemoveLocal(service);
service = CService();

#ifdef ENABLE_TOR_SUBPROCESS
if (m_try_exec && !m_execute.empty()) {
LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to launch via %s\n", m_current_tor_control_center, m_execute);
try {
m_current_tor_control_center = LaunchTor();
Reconnect();
return;
} catch (...) {
// fall through to normal reconnect logic
}
}
#endif

if (!reconnect)
return;

LogPrint(BCLog::TOR, "Not connected to Tor control port %s, trying to reconnect\n", m_tor_control_center);
LogPrint(BCLog::TOR, "Not connected to Tor control port %s, trying to reconnect in %s seconds\n", m_current_tor_control_center, reconnect_timeout);
m_current_tor_control_center = m_connect_tor_control_center;
m_try_exec = true; // if this fails

// Single-shot timer for reconnect. Use exponential backoff.
struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0));
Expand All @@ -645,9 +750,9 @@ void TorController::Reconnect()
/* Try to reconnect and reestablish if we get booted - for example, Tor
* may be restarting.
*/
if (!conn.Connect(m_tor_control_center, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
if (!conn.Connect(m_current_tor_control_center, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) {
LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", m_tor_control_center);
LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", m_current_tor_control_center);
}
}

Expand All @@ -668,7 +773,17 @@ static std::thread torControlThread;

static void TorControlThread(CService onion_service_target)
{
TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL), onion_service_target);
#ifdef ENABLE_TOR_SUBPROCESS
std::string execute_command = gArgs.GetArg("-torexecute", DEFAULT_TOR_EXECUTE);
if (execute_command == "1") {
execute_command = DEFAULT_TOR_EXECUTE;
} else if (execute_command == "0") {
execute_command.clear();
}
#else
const std::string execute_command;
#endif
TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL), onion_service_target, execute_command);

event_base_dispatch(gBase);
}
Expand Down
24 changes: 21 additions & 3 deletions src/torcontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@
#ifndef BITCOIN_TORCONTROL_H
#define BITCOIN_TORCONTROL_H

#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif

#include <netaddress.h>
#include <util/fs.h>

#ifdef ENABLE_TOR_SUBPROCESS
#include <util/subprocess.h>
#endif // ENABLE_TOR_SUBPROCESS

#include <event2/util.h>

#include <cstdint>
Expand All @@ -21,6 +29,7 @@

constexpr int DEFAULT_TOR_CONTROL_PORT = 9051;
extern const std::string DEFAULT_TOR_CONTROL;
extern const std::string DEFAULT_TOR_EXECUTE;
static const bool DEFAULT_LISTEN_ONION = true;

void StartTorControl(CService onion_service_target);
Expand Down Expand Up @@ -53,6 +62,7 @@ class TorControlConnection
public:
typedef std::function<void(TorControlConnection&)> ConnectionCB;
typedef std::function<void(TorControlConnection &,const TorControlReply &)> ReplyHandlerCB;
static void IgnoreReplyHandler(TorControlConnection &, const TorControlReply &);

/** Create a new TorControlConnection.
*/
Expand All @@ -77,7 +87,7 @@ class TorControlConnection
* A trailing CRLF is automatically added.
* Return true on success.
*/
bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler);
bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler = IgnoreReplyHandler);

private:
/** Callback when ready for use */
Expand Down Expand Up @@ -106,7 +116,7 @@ class TorControlConnection
class TorController
{
public:
TorController(struct event_base* base, const std::string& tor_control_center, const CService& target);
TorController(struct event_base* base, const std::string& tor_control_center, const CService& target, const std::string& execute);
TorController() : conn{nullptr} {
// Used for testing only.
}
Expand All @@ -119,13 +129,19 @@ class TorController
void Reconnect();
private:
struct event_base* base;
const std::string m_tor_control_center;
const std::string m_connect_tor_control_center;
std::string m_current_tor_control_center;
TorControlConnection conn;
std::string private_key;
std::string service_id;
bool m_try_exec{true};
bool reconnect;
struct event *reconnect_ev = nullptr;
float reconnect_timeout;
std::string m_execute{DEFAULT_TOR_EXECUTE};
#ifdef ENABLE_TOR_SUBPROCESS
subprocess::Popen *m_process{nullptr};
#endif
CService service;
const CService m_target;
/** Cookie for SAFECOOKIE auth */
Expand All @@ -151,6 +167,8 @@ class TorController

/** Callback for reconnect timer */
static void reconnect_cb(evutil_socket_t fd, short what, void *arg);

std::string LaunchTor();
};

#endif // BITCOIN_TORCONTROL_H
Loading

0 comments on commit e1240bc

Please sign in to comment.