Skip to content

Commit

Permalink
torcontrol: Launch a private Tor instance when not already running
Browse files Browse the repository at this point in the history
  • Loading branch information
luke-jr committed Feb 15, 2019
1 parent 6543570 commit 61e1a11
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/init.cpp
Expand Up @@ -410,6 +410,7 @@ void SetupServerArgs()
gArgs.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), false, OptionsCategory::CONNECTION);
gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), true, OptionsCategory::CONNECTION);
gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), false, OptionsCategory::CONNECTION);
gArgs.AddArg("-torexecute=<command>", strprintf("Tor command to use if not already running (default: %s)", DEFAULT_TOR_EXECUTE), false, OptionsCategory::CONNECTION);
gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", false, OptionsCategory::CONNECTION);
#ifdef USE_UPNP
#if USE_UPNP
Expand Down
112 changes: 102 additions & 10 deletions src/torcontrol.cpp
Expand Up @@ -10,6 +10,7 @@
#include <util/system.h>
#include <crypto/hmac_sha256.h>

#include <chrono>
#include <vector>
#include <deque>
#include <set>
Expand All @@ -19,6 +20,7 @@
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/process.hpp>

#include <event2/bufferevent.h>
#include <event2/buffer.h>
Expand All @@ -28,6 +30,7 @@

/** Default control port */
const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:9051";
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 @@ -72,6 +75,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 @@ -96,7 +100,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);

/** Response handlers for async replies */
boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler;
Expand Down Expand Up @@ -130,6 +134,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 @@ -409,7 +417,7 @@ static bool WriteBinaryFile(const fs::path &filename, const std::string &data)
class TorController
{
public:
TorController(struct event_base* base, const std::string& target);
TorController(struct event_base* base, const std::string& target, const std::string& execute);
~TorController();

/** Get name fo file to store private key in */
Expand All @@ -419,13 +427,17 @@ class TorController
void Reconnect();
private:
struct event_base* base;
std::string target;
std::string m_connect_target;
std::string m_current_target;
TorControlConnection conn;
std::string private_key;
std::string service_id;
bool m_try_exec{true};
bool reconnect;
struct event *reconnect_ev;
float reconnect_timeout;
std::string m_execute{DEFAULT_TOR_EXECUTE};
boost::process::child *m_process;
CService service;
/** Cookie for SAFECOOKIE auth */
std::vector<uint8_t> cookie;
Expand All @@ -447,17 +459,21 @@ class TorController

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

std::string LaunchTor();
};

TorController::TorController(struct event_base* _base, const std::string& _target):
TorController::TorController(struct event_base* _base, const std::string& _target, const std::string& execute):
base(_base),
target(_target), conn(base), reconnect(true), reconnect_ev(0),
reconnect_timeout(RECONNECT_TIMEOUT_START)
m_connect_target(_target), conn(base), reconnect(true), reconnect_ev(0),
reconnect_timeout(RECONNECT_TIMEOUT_START),
m_execute(execute)
{
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
m_current_target = _target;
if (!conn.Connect(_target, 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", _target);
Expand All @@ -479,6 +495,10 @@ TorController::~TorController()
if (service.IsValid()) {
RemoveLocal(service);
}
if (m_process) {
conn.Command("SIGNAL SHUTDOWN");
delete m_process;
}
}

void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply)
Expand Down Expand Up @@ -521,6 +541,10 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
if (reply.code == 250) {
LogPrint(BCLog::TOR, "tor: Authentication successful\n");

if (m_process) {
_conn.Command("TAKEOWNERSHIP");
}

// 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", "") == "") {
Expand Down Expand Up @@ -681,22 +705,84 @@ 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()
{
fs::path tor_datadir = GetDataDir() / "tor";
std::string controlport_env_filepath = (tor_datadir / "controlport.env").string();
std::remove(controlport_env_filepath.c_str());

if (m_process) {
m_process->terminate();
delete m_process;
}

boost::process::opstream in;
try {
m_process = new boost::process::child(m_execute + " -f -", boost::process::std_in < in);
} catch (...) {
LogPrint(BCLog::TOR, "tor: Failed to execute Tor process\n");
throw;
}
in << "SOCKSPort 0" << std::endl;
in << "DataDirectory " << tor_datadir.string() << std::endl;
in << "ControlPort auto" << std::endl;
in << "ControlPortWriteToFile " << controlport_env_filepath << std::endl;
in << "CookieAuthentication 1" << std::endl;
in.pipe().close();

while (!fs::exists(controlport_env_filepath)) {
if (!m_process->running()) {
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->terminate();
delete m_process;
m_process = nullptr;
throw std::runtime_error("port line unrecognized");
}

return portline.substr(5);
}

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

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_target, m_execute);
try {
m_current_target = LaunchTor();
Reconnect();
return;
} catch (...) {
// fall through to normal reconnect logic
}
}

if (!reconnect)
return;

LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to reconnect\n", target);
LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to reconnect in %s seconds\n", m_current_target, reconnect_timeout);
m_current_target = m_connect_target;
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 @@ -710,9 +796,9 @@ void TorController::Reconnect()
/* Try to reconnect and reestablish if we get booted - for example, Tor
* may be restarting.
*/
if (!conn.Connect(target, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
if (!conn.Connect(m_current_target, 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", target);
LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", m_current_target);
}
}

Expand All @@ -733,7 +819,13 @@ static std::thread torControlThread;

static void TorControlThread()
{
TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL));
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();
}
TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL), execute_command);

event_base_dispatch(gBase);
}
Expand Down
1 change: 1 addition & 0 deletions src/torcontrol.h
Expand Up @@ -11,6 +11,7 @@
#include <scheduler.h>

extern const std::string DEFAULT_TOR_CONTROL;
extern const std::string DEFAULT_TOR_EXECUTE;
static const bool DEFAULT_LISTEN_ONION = true;

void StartTorControl();
Expand Down
1 change: 1 addition & 0 deletions test/lint/lint-includes.sh
Expand Up @@ -61,6 +61,7 @@ EXPECTED_BOOST_INCLUDES=(
boost/optional.hpp
boost/preprocessor/cat.hpp
boost/preprocessor/stringize.hpp
boost/process.hpp
boost/signals2/connection.hpp
boost/signals2/last_value.hpp
boost/signals2/signal.hpp
Expand Down

0 comments on commit 61e1a11

Please sign in to comment.