Merge bitcoin/bitcoin#15423: torcontrol: Query Tor for correct -onion configuration

b2774fc0be torcontrol: Query Tor for correct -onion configuration (Luke Dashjr)

Pull request description:

  Currently, we just assume any running Tor instance provides localhost port 9050 for SOCKS, and configure `-onion` accordingly when we get a Tor control connection.

  This actually queries the Tor node for its SOCKS listeners, and uses the configured port instead.

  For backward compatibility, it falls back to localhost:9050 if it can't get any better port info. I'm not sure if that's the correct action to take when the Tor daemon explicitly says there are no ports listening...

ACKs for top commit:
  laanwj:
    Tested ACK (FreeBSD) b2774fc0be
  vasild:
    ACK b2774fc0be
  jonatack:
    ACK b2774fc0be review, rebased to master, debug build, ran unit tests, tested happy path only

Tree-SHA512: 2fa93a3cf0cb675801d1b51322ce953ea9b2317f78154a53b603244d74252f434cc1eaa5ae48cb3fe6bdc4ce984a6d976ff95bb046f7933b9740332942378c02
This commit is contained in:
laanwj 2022-03-22 11:57:09 +01:00
commit 2948d6dea0
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
2 changed files with 71 additions and 19 deletions

View file

@ -53,6 +53,7 @@ static const float RECONNECT_TIMEOUT_EXP = 1.5;
* this is belt-and-suspenders sanity limit to prevent memory exhaustion.
*/
static const int MAX_LINE_LENGTH = 100000;
static const uint16_t DEFAULT_TOR_SOCKS_PORT = 9050;
/****** Low-level TorControlConnection ********/
@ -338,6 +339,73 @@ TorController::~TorController()
}
}
void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlReply& reply)
{
// NOTE: We can only get here if -onion is unset
std::string socks_location;
if (reply.code == 250) {
for (const auto& line : reply.lines) {
if (0 == line.compare(0, 20, "net/listeners/socks=")) {
const std::string port_list_str = line.substr(20);
std::vector<std::string> port_list;
boost::split(port_list, port_list_str, boost::is_any_of(" "));
for (auto& portstr : port_list) {
if (portstr.empty()) continue;
if ((portstr[0] == '"' || portstr[0] == '\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) {
portstr = portstr.substr(1, portstr.size() - 2);
if (portstr.empty()) continue;
}
socks_location = portstr;
if (0 == portstr.compare(0, 10, "127.0.0.1:")) {
// Prefer localhost - ignore other ports
break;
}
}
}
}
if (!socks_location.empty()) {
LogPrint(BCLog::TOR, "tor: Get SOCKS port command yielded %s\n", socks_location);
} else {
LogPrintf("tor: Get SOCKS port command returned nothing\n");
}
} else if (reply.code == 510) { // 510 Unrecognized command
LogPrintf("tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)\n");
} else {
LogPrintf("tor: Get SOCKS port command failed; error code %d\n", reply.code);
}
CService resolved;
Assume(!resolved.IsValid());
if (!socks_location.empty()) {
resolved = LookupNumeric(socks_location, DEFAULT_TOR_SOCKS_PORT);
}
if (!resolved.IsValid()) {
// Fallback to old behaviour
resolved = LookupNumeric("127.0.0.1", DEFAULT_TOR_SOCKS_PORT);
}
Assume(resolved.IsValid());
LogPrint(BCLog::TOR, "tor: Configuring onion proxy for %s\n", resolved.ToStringIPPort());
Proxy addrOnion = Proxy(resolved, true);
SetProxy(NET_ONION, addrOnion);
const auto onlynets = gArgs.GetArgs("-onlynet");
const bool onion_allowed_by_onlynet{
!gArgs.IsArgSet("-onlynet") ||
std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {
return ParseNetwork(n) == NET_ONION;
})};
if (onion_allowed_by_onlynet) {
// If NET_ONION is reachable, then the below is a noop.
//
// If NET_ONION is not reachable, then none of -proxy or -onion was given.
// Since we are here, then -torcontrol and -torpassword were given.
SetReachable(NET_ONION, true);
}
}
void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply)
{
if (reply.code == 250) {
@ -381,25 +449,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
// 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", "") == "") {
CService resolved(LookupNumeric("127.0.0.1", 9050));
Proxy addrOnion = Proxy(resolved, true);
SetProxy(NET_ONION, addrOnion);
const auto onlynets = gArgs.GetArgs("-onlynet");
const bool onion_allowed_by_onlynet{
!gArgs.IsArgSet("-onlynet") ||
std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {
return ParseNetwork(n) == NET_ONION;
})};
if (onion_allowed_by_onlynet) {
// If NET_ONION is reachable, then the below is a noop.
//
// If NET_ONION is not reachable, then none of -proxy or -onion was given.
// Since we are here, then -torcontrol and -torpassword were given.
SetReachable(NET_ONION, true);
}
_conn.Command("GETINFO net/listeners/socks", std::bind(&TorController::get_socks_cb, this, std::placeholders::_1, std::placeholders::_2));
}
// Finally - now create the service

View file

@ -140,6 +140,8 @@ private:
std::vector<uint8_t> clientNonce;
public:
/** Callback for GETINFO net/listeners/socks result */
void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply);
/** Callback for ADD_ONION result */
void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply);
/** Callback for AUTHENTICATE result */