Changeset View
Changeset View
Standalone View
Standalone View
src/torcontrol.cpp
Show First 20 Lines • Show All 173 Lines • ▼ Show 20 Lines | while ((line = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF)) != | ||||
self->message.Clear(); | self->message.Clear(); | ||||
} | } | ||||
} | } | ||||
// Check for size of buffer - protect against memory exhaustion with very | // Check for size of buffer - protect against memory exhaustion with very | ||||
// long lines. Do this after evbuffer_readln to make sure all full lines | // long lines. Do this after evbuffer_readln to make sure all full lines | ||||
// have been removed from the buffer. Everything left is an incomplete line. | // have been removed from the buffer. Everything left is an incomplete line. | ||||
if (evbuffer_get_length(input) > MAX_LINE_LENGTH) { | if (evbuffer_get_length(input) > MAX_LINE_LENGTH) { | ||||
LogPrintf("tor: Disconnecting because MAX_LINE_LENGTH exceeded\n"); | LogPrint(BCLog::TOR, | ||||
"tor: Disconnecting because MAX_LINE_LENGTH exceeded\n"); | |||||
self->Disconnect(); | self->Disconnect(); | ||||
} | } | ||||
} | } | ||||
void TorControlConnection::eventcb(struct bufferevent *bev, short what, | void TorControlConnection::eventcb(struct bufferevent *bev, short what, | ||||
void *ctx) { | void *ctx) { | ||||
TorControlConnection *self = (TorControlConnection *)ctx; | TorControlConnection *self = (TorControlConnection *)ctx; | ||||
if (what & BEV_EVENT_CONNECTED) { | if (what & BEV_EVENT_CONNECTED) { | ||||
Show All 15 Lines | bool TorControlConnection::Connect(const std::string &target, | ||||
const ConnectionCB &_disconnected) { | const ConnectionCB &_disconnected) { | ||||
if (b_conn) Disconnect(); | if (b_conn) Disconnect(); | ||||
// Parse target address:port | // Parse target address:port | ||||
struct sockaddr_storage connect_to_addr; | struct sockaddr_storage connect_to_addr; | ||||
int connect_to_addrlen = sizeof(connect_to_addr); | int connect_to_addrlen = sizeof(connect_to_addr); | ||||
if (evutil_parse_sockaddr_port(target.c_str(), | if (evutil_parse_sockaddr_port(target.c_str(), | ||||
(struct sockaddr *)&connect_to_addr, | (struct sockaddr *)&connect_to_addr, | ||||
&connect_to_addrlen) < 0) { | &connect_to_addrlen) < 0) { | ||||
LogPrintf("tor: Error parsing socket address %s\n", target); | LogPrint(BCLog::TOR, "tor: Error parsing socket address %s\n", target); | ||||
return false; | return false; | ||||
} | } | ||||
// Create a new socket, set up callbacks and enable notification bits | // Create a new socket, set up callbacks and enable notification bits | ||||
b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); | b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); | ||||
if (!b_conn) return false; | if (!b_conn) return false; | ||||
bufferevent_setcb(b_conn, TorControlConnection::readcb, nullptr, | bufferevent_setcb(b_conn, TorControlConnection::readcb, nullptr, | ||||
TorControlConnection::eventcb, this); | TorControlConnection::eventcb, this); | ||||
bufferevent_enable(b_conn, EV_READ | EV_WRITE); | bufferevent_enable(b_conn, EV_READ | EV_WRITE); | ||||
this->connected = _connected; | this->connected = _connected; | ||||
this->disconnected = _disconnected; | this->disconnected = _disconnected; | ||||
// Finally, connect to target | // Finally, connect to target | ||||
if (bufferevent_socket_connect(b_conn, (struct sockaddr *)&connect_to_addr, | if (bufferevent_socket_connect(b_conn, (struct sockaddr *)&connect_to_addr, | ||||
connect_to_addrlen) < 0) { | connect_to_addrlen) < 0) { | ||||
LogPrintf("tor: Error connecting to address %s\n", target); | LogPrint(BCLog::TOR, "tor: Error connecting to address %s\n", target); | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool TorControlConnection::Disconnect() { | bool TorControlConnection::Disconnect() { | ||||
if (b_conn) bufferevent_free(b_conn); | if (b_conn) bufferevent_free(b_conn); | ||||
b_conn = 0; | b_conn = 0; | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | while (ptr < s.size()) { | ||||
value.push_back(s[ptr]); | value.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
} | } | ||||
// unexpected end of line | // unexpected end of line | ||||
if (ptr == s.size()) return std::map<std::string, std::string>(); | if (ptr == s.size()) return std::map<std::string, std::string>(); | ||||
// skip closing '"' | // skip closing '"' | ||||
++ptr; | ++ptr; | ||||
/* TODO: unescape value - according to the spec this depends on the | /* TODO: unescape value - according to the spec this depends on the | ||||
* context, some strings use C-LogPrintf style escape codes, some | * context, some strings use C-printf style escape codes, some | ||||
* don't. So may be better handled at the call site. | * don't. So may be better handled at the call site. | ||||
*/ | */ | ||||
} else { | } else { | ||||
// Unquoted value. Note that values can contain '=' at will, just no | // Unquoted value. Note that values can contain '=' at will, just no | ||||
// spaces | // spaces | ||||
while (ptr < s.size() && s[ptr] != ' ') { | while (ptr < s.size() && s[ptr] != ' ') { | ||||
value.push_back(s[ptr]); | value.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
▲ Show 20 Lines • Show All 103 Lines • ▼ Show 20 Lines | |||||
}; | }; | ||||
TorController::TorController(struct event_base *_base, | TorController::TorController(struct event_base *_base, | ||||
const std::string &_target) | const std::string &_target) | ||||
: base(_base), target(_target), conn(base), reconnect(true), | : base(_base), target(_target), conn(base), reconnect(true), | ||||
reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { | reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { | ||||
reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); | reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); | ||||
if (!reconnect_ev) | if (!reconnect_ev) | ||||
LogPrintf( | LogPrint( | ||||
BCLog::TOR, | |||||
"tor: Failed to create event for reconnection: out of memory?\n"); | "tor: Failed to create event for reconnection: out of memory?\n"); | ||||
// Start connection attempts immediately | // Start connection attempts immediately | ||||
if (!conn.Connect(_target, | if (!conn.Connect(_target, | ||||
boost::bind(&TorController::connected_cb, this, _1), | boost::bind(&TorController::connected_cb, this, _1), | ||||
boost::bind(&TorController::disconnected_cb, this, _1))) { | boost::bind(&TorController::disconnected_cb, this, _1))) { | ||||
LogPrintf("tor: Initiating connection to Tor control port %s failed\n", | LogPrint(BCLog::TOR, | ||||
"tor: Initiating connection to Tor control port %s failed\n", | |||||
_target); | _target); | ||||
} | } | ||||
// Read service private key if cached | // Read service private key if cached | ||||
std::pair<bool, std::string> pkf = ReadBinaryFile(GetPrivateKeyFile()); | std::pair<bool, std::string> pkf = ReadBinaryFile(GetPrivateKeyFile()); | ||||
if (pkf.first) { | if (pkf.first) { | ||||
LogPrint(BCLog::TOR, "tor: Reading cached private key from %s\n", | LogPrint(BCLog::TOR, "tor: Reading cached private key from %s\n", | ||||
GetPrivateKeyFile()); | GetPrivateKeyFile()); | ||||
private_key = pkf.second; | private_key = pkf.second; | ||||
} | } | ||||
Show All 16 Lines | if (reply.code == 250) { | ||||
for (const std::string &s : reply.lines) { | for (const std::string &s : reply.lines) { | ||||
std::map<std::string, std::string> m = ParseTorReplyMapping(s); | std::map<std::string, std::string> m = ParseTorReplyMapping(s); | ||||
std::map<std::string, std::string>::iterator i; | std::map<std::string, std::string>::iterator i; | ||||
if ((i = m.find("ServiceID")) != m.end()) service_id = i->second; | if ((i = m.find("ServiceID")) != m.end()) service_id = i->second; | ||||
if ((i = m.find("PrivateKey")) != m.end()) private_key = i->second; | if ((i = m.find("PrivateKey")) != m.end()) private_key = i->second; | ||||
} | } | ||||
service = LookupNumeric(std::string(service_id + ".onion").c_str(), | service = LookupNumeric(std::string(service_id + ".onion").c_str(), | ||||
GetListenPort()); | GetListenPort()); | ||||
LogPrintf("tor: Got service ID %s, advertising service %s\n", | LogPrint(BCLog::TOR, "tor: Got service ID %s, advertising service %s\n", | ||||
service_id, service.ToString()); | service_id, service.ToString()); | ||||
if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { | if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { | ||||
LogPrint(BCLog::TOR, "tor: Cached service private key to %s\n", | LogPrint(BCLog::TOR, "tor: Cached service private key to %s\n", | ||||
GetPrivateKeyFile()); | GetPrivateKeyFile()); | ||||
} else { | } else { | ||||
LogPrintf("tor: Error writing service private key to %s\n", | LogPrint(BCLog::TOR, | ||||
"tor: Error writing service private key to %s\n", | |||||
GetPrivateKeyFile()); | GetPrivateKeyFile()); | ||||
} | } | ||||
AddLocal(service, LOCAL_MANUAL); | AddLocal(service, LOCAL_MANUAL); | ||||
// ... onion requested - keep connection open | // ... onion requested - keep connection open | ||||
} else if (reply.code == 510) { // 510 Unrecognized command | } else if (reply.code == 510) { // 510 Unrecognized command | ||||
LogPrintf("tor: Add onion failed with unrecognized command (You " | LogPrint(BCLog::TOR, | ||||
"tor: Add onion failed with unrecognized command (You " | |||||
"probably need to upgrade Tor)\n"); | "probably need to upgrade Tor)\n"); | ||||
} else { | } else { | ||||
LogPrintf("tor: Add onion failed; error code %d\n", reply.code); | LogPrint(BCLog::TOR, "tor: Add onion failed; error code %d\n", | ||||
reply.code); | |||||
} | } | ||||
} | } | ||||
void TorController::auth_cb(TorControlConnection &_conn, | void TorController::auth_cb(TorControlConnection &_conn, | ||||
const TorControlReply &reply) { | const TorControlReply &reply) { | ||||
if (reply.code == 250) { | if (reply.code == 250) { | ||||
LogPrint(BCLog::TOR, "tor: Authentication successful\n"); | LogPrint(BCLog::TOR, "tor: Authentication successful\n"); | ||||
Show All 15 Lines | if (reply.code == 250) { | ||||
// Request hidden service, redirect port. | // Request hidden service, redirect port. | ||||
// Note that the 'virtual' port doesn't have to be the same as our | // Note that the 'virtual' port doesn't have to be the same as our | ||||
// internal port, but this is just a convenient choice. TODO; refactor | // internal port, but this is just a convenient choice. TODO; refactor | ||||
// the shutdown sequence some day. | // the shutdown sequence some day. | ||||
_conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", | _conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", | ||||
private_key, GetListenPort(), GetListenPort()), | private_key, GetListenPort(), GetListenPort()), | ||||
boost::bind(&TorController::add_onion_cb, this, _1, _2)); | boost::bind(&TorController::add_onion_cb, this, _1, _2)); | ||||
} else { | } else { | ||||
LogPrintf("tor: Authentication failed\n"); | LogPrint(BCLog::TOR, "tor: Authentication failed\n"); | ||||
} | } | ||||
} | } | ||||
/** Compute Tor SAFECOOKIE response. | /** Compute Tor SAFECOOKIE response. | ||||
* | * | ||||
* ServerHash is computed as: | * ServerHash is computed as: | ||||
* HMAC-SHA256("Tor safe cookie authentication server-to-controller hash", | * HMAC-SHA256("Tor safe cookie authentication server-to-controller hash", | ||||
* CookieString | ClientNonce | ServerNonce) | * CookieString | ClientNonce | ServerNonce) | ||||
Show All 32 Lines | if (reply.code == 250) { | ||||
std::map<std::string, std::string> m = | std::map<std::string, std::string> m = | ||||
ParseTorReplyMapping(l.second); | ParseTorReplyMapping(l.second); | ||||
std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); | std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); | ||||
std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); | std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); | ||||
LogPrint(BCLog::TOR, | LogPrint(BCLog::TOR, | ||||
"tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", | "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", | ||||
HexStr(serverHash), HexStr(serverNonce)); | HexStr(serverHash), HexStr(serverNonce)); | ||||
if (serverNonce.size() != 32) { | if (serverNonce.size() != 32) { | ||||
LogPrintf( | LogPrint( | ||||
BCLog::TOR, | |||||
"tor: ServerNonce is not 32 bytes, as required by spec\n"); | "tor: ServerNonce is not 32 bytes, as required by spec\n"); | ||||
return; | return; | ||||
} | } | ||||
std::vector<uint8_t> computedServerHash = ComputeResponse( | std::vector<uint8_t> computedServerHash = ComputeResponse( | ||||
TOR_SAFE_SERVERKEY, cookie, clientNonce, serverNonce); | TOR_SAFE_SERVERKEY, cookie, clientNonce, serverNonce); | ||||
if (computedServerHash != serverHash) { | if (computedServerHash != serverHash) { | ||||
LogPrintf("tor: ServerHash %s does not match expected " | LogPrint(BCLog::TOR, | ||||
"tor: ServerHash %s does not match expected " | |||||
"ServerHash %s\n", | "ServerHash %s\n", | ||||
HexStr(serverHash), HexStr(computedServerHash)); | HexStr(serverHash), HexStr(computedServerHash)); | ||||
return; | return; | ||||
} | } | ||||
std::vector<uint8_t> computedClientHash = ComputeResponse( | std::vector<uint8_t> computedClientHash = ComputeResponse( | ||||
TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); | TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); | ||||
_conn.Command("AUTHENTICATE " + HexStr(computedClientHash), | _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), | ||||
boost::bind(&TorController::auth_cb, this, _1, _2)); | boost::bind(&TorController::auth_cb, this, _1, _2)); | ||||
} else { | } else { | ||||
LogPrintf("tor: Invalid reply to AUTHCHALLENGE\n"); | LogPrint(BCLog::TOR, "tor: Invalid reply to AUTHCHALLENGE\n"); | ||||
} | } | ||||
} else { | } else { | ||||
LogPrintf("tor: SAFECOOKIE authentication challenge failed\n"); | LogPrint(BCLog::TOR, | ||||
"tor: SAFECOOKIE authentication challenge failed\n"); | |||||
} | } | ||||
} | } | ||||
void TorController::protocolinfo_cb(TorControlConnection &_conn, | void TorController::protocolinfo_cb(TorControlConnection &_conn, | ||||
const TorControlReply &reply) { | const TorControlReply &reply) { | ||||
if (reply.code == 250) { | if (reply.code == 250) { | ||||
std::set<std::string> methods; | std::set<std::string> methods; | ||||
std::string cookiefile; | std::string cookiefile; | ||||
Show All 38 Lines | if (reply.code == 250) { | ||||
if (methods.count("HASHEDPASSWORD")) { | if (methods.count("HASHEDPASSWORD")) { | ||||
LogPrint(BCLog::TOR, | LogPrint(BCLog::TOR, | ||||
"tor: Using HASHEDPASSWORD authentication\n"); | "tor: Using HASHEDPASSWORD authentication\n"); | ||||
boost::replace_all(torpassword, "\"", "\\\""); | boost::replace_all(torpassword, "\"", "\\\""); | ||||
_conn.Command( | _conn.Command( | ||||
"AUTHENTICATE \"" + torpassword + "\"", | "AUTHENTICATE \"" + torpassword + "\"", | ||||
boost::bind(&TorController::auth_cb, this, _1, _2)); | boost::bind(&TorController::auth_cb, this, _1, _2)); | ||||
} else { | } else { | ||||
LogPrintf("tor: Password provided with -torpassword, but " | LogPrint(BCLog::TOR, | ||||
"tor: Password provided with -torpassword, but " | |||||
"HASHEDPASSWORD authentication is not available\n"); | "HASHEDPASSWORD authentication is not available\n"); | ||||
} | } | ||||
} else if (methods.count("NULL")) { | } else if (methods.count("NULL")) { | ||||
LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); | LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); | ||||
_conn.Command("AUTHENTICATE", | _conn.Command("AUTHENTICATE", | ||||
boost::bind(&TorController::auth_cb, this, _1, _2)); | boost::bind(&TorController::auth_cb, this, _1, _2)); | ||||
} else if (methods.count("SAFECOOKIE")) { | } else if (methods.count("SAFECOOKIE")) { | ||||
// Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie | // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie | ||||
LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, " | LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, " | ||||
Show All 9 Lines | if (reply.code == 250) { | ||||
status_cookie.second.end()); | status_cookie.second.end()); | ||||
clientNonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0); | clientNonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0); | ||||
GetRandBytes(&clientNonce[0], TOR_NONCE_SIZE); | GetRandBytes(&clientNonce[0], TOR_NONCE_SIZE); | ||||
_conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), | _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), | ||||
boost::bind(&TorController::authchallenge_cb, | boost::bind(&TorController::authchallenge_cb, | ||||
this, _1, _2)); | this, _1, _2)); | ||||
} else { | } else { | ||||
if (status_cookie.first) { | if (status_cookie.first) { | ||||
LogPrintf("tor: Authentication cookie %s is not exactly %i " | LogPrint(BCLog::TOR, | ||||
"tor: Authentication cookie %s is not exactly %i " | |||||
"bytes, as is required by the spec\n", | "bytes, as is required by the spec\n", | ||||
cookiefile, TOR_COOKIE_SIZE); | cookiefile, TOR_COOKIE_SIZE); | ||||
} else { | } else { | ||||
LogPrintf("tor: Authentication cookie %s could not be " | LogPrint(BCLog::TOR, | ||||
"tor: Authentication cookie %s could not be " | |||||
"opened (check permissions)\n", | "opened (check permissions)\n", | ||||
cookiefile); | cookiefile); | ||||
} | } | ||||
} | } | ||||
} else if (methods.count("HASHEDPASSWORD")) { | } else if (methods.count("HASHEDPASSWORD")) { | ||||
LogPrintf("tor: The only supported authentication mechanism left " | LogPrint(BCLog::TOR, | ||||
"tor: The only supported authentication mechanism left " | |||||
"is password, but no password provided with " | "is password, but no password provided with " | ||||
"-torpassword\n"); | "-torpassword\n"); | ||||
} else { | } else { | ||||
LogPrintf("tor: No supported authentication method\n"); | LogPrint(BCLog::TOR, "tor: No supported authentication method\n"); | ||||
} | } | ||||
} else { | } else { | ||||
LogPrintf("tor: Requesting protocol info failed\n"); | LogPrint(BCLog::TOR, "tor: Requesting protocol info failed\n"); | ||||
} | } | ||||
} | } | ||||
void TorController::connected_cb(TorControlConnection &_conn) { | void TorController::connected_cb(TorControlConnection &_conn) { | ||||
reconnect_timeout = RECONNECT_TIMEOUT_START; | reconnect_timeout = RECONNECT_TIMEOUT_START; | ||||
// First send a PROTOCOLINFO command to figure out what authentication is | // First send a PROTOCOLINFO command to figure out what authentication is | ||||
// expected | // expected | ||||
if (!_conn.Command( | if (!_conn.Command( | ||||
"PROTOCOLINFO 1", | "PROTOCOLINFO 1", | ||||
boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) | boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) | ||||
LogPrintf("tor: Error sending initial protocolinfo command\n"); | LogPrint(BCLog::TOR, | ||||
"tor: Error sending initial protocolinfo command\n"); | |||||
} | } | ||||
void TorController::disconnected_cb(TorControlConnection &_conn) { | void TorController::disconnected_cb(TorControlConnection &_conn) { | ||||
// Stop advertising service when disconnected | // Stop advertising service when disconnected | ||||
if (service.IsValid()) RemoveLocal(service); | if (service.IsValid()) RemoveLocal(service); | ||||
service = CService(); | service = CService(); | ||||
if (!reconnect) return; | if (!reconnect) return; | ||||
Show All 9 Lines | |||||
void TorController::Reconnect() { | void TorController::Reconnect() { | ||||
/* Try to reconnect and reestablish if we get booted - for example, Tor may | /* Try to reconnect and reestablish if we get booted - for example, Tor may | ||||
* be restarting. | * be restarting. | ||||
*/ | */ | ||||
if (!conn.Connect(target, | if (!conn.Connect(target, | ||||
boost::bind(&TorController::connected_cb, this, _1), | boost::bind(&TorController::connected_cb, this, _1), | ||||
boost::bind(&TorController::disconnected_cb, this, _1))) { | boost::bind(&TorController::disconnected_cb, this, _1))) { | ||||
LogPrintf( | LogPrint( | ||||
BCLog::TOR, | |||||
"tor: Re-initiating connection to Tor control port %s failed\n", | "tor: Re-initiating connection to Tor control port %s failed\n", | ||||
target); | target); | ||||
} | } | ||||
} | } | ||||
fs::path TorController::GetPrivateKeyFile() { | fs::path TorController::GetPrivateKeyFile() { | ||||
return GetDataDir() / "onion_private_key"; | return GetDataDir() / "onion_private_key"; | ||||
} | } | ||||
Show All 17 Lines | void StartTorControl() { | ||||
assert(!gBase); | assert(!gBase); | ||||
#ifdef WIN32 | #ifdef WIN32 | ||||
evthread_use_windows_threads(); | evthread_use_windows_threads(); | ||||
#else | #else | ||||
evthread_use_pthreads(); | evthread_use_pthreads(); | ||||
#endif | #endif | ||||
gBase = event_base_new(); | gBase = event_base_new(); | ||||
if (!gBase) { | if (!gBase) { | ||||
LogPrintf("tor: Unable to create event_base\n"); | LogPrint(BCLog::TOR, "tor: Unable to create event_base\n"); | ||||
return; | return; | ||||
} | } | ||||
torControlThread = std::thread( | torControlThread = std::thread( | ||||
std::bind(&TraceThread<void (*)()>, "torcontrol", &TorControlThread)); | std::bind(&TraceThread<void (*)()>, "torcontrol", &TorControlThread)); | ||||
} | } | ||||
void InterruptTorControl() { | void InterruptTorControl() { | ||||
if (gBase) { | if (gBase) { | ||||
LogPrintf("tor: Thread interrupt\n"); | LogPrint(BCLog::TOR, "tor: Thread interrupt\n"); | ||||
event_base_loopbreak(gBase); | event_base_loopbreak(gBase); | ||||
} | } | ||||
} | } | ||||
void StopTorControl() { | void StopTorControl() { | ||||
if (gBase) { | if (gBase) { | ||||
torControlThread.join(); | torControlThread.join(); | ||||
event_base_free(gBase); | event_base_free(gBase); | ||||
gBase = nullptr; | gBase = nullptr; | ||||
} | } | ||||
} | } |