Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
252/** Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'.253* Returns a map of keys to values, or an empty map if there was an error.254* Grammar is implicitly defined in https://spec.torproject.org/control-spec by255* the server reply formats for PROTOCOLINFO (S3.21), AUTHCHALLENGE (S3.24),256* and ADD_ONION (S3.27). See also sections 2.1 and 2.3.257*/258std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s)259{260std::map<std::string,std::string> mapping;261size_t ptr=0;262while (ptr < s.size()) {263std::string key, value;264while (ptr < s.size() && s[ptr] != '=' && s[ptr] != ' ') {265key.push_back(s[ptr]);266++ptr;267}268if (ptr == s.size()) // unexpected end of line269return std::map<std::string,std::string>();270if (s[ptr] == ' ') // The remaining string is an OptArguments271break;272++ptr; // skip '='273if (ptr < s.size() && s[ptr] == '"') { // Quoted string274++ptr; // skip opening '"'275bool escape_next = false;276while (ptr < s.size() && (escape_next || s[ptr] != '"')) {277// Repeated backslashes must be interpreted as pairs278escape_next = (s[ptr] == '\\' && !escape_next);279value.push_back(s[ptr]);280++ptr;281}282if (ptr == s.size()) // unexpected end of line283return std::map<std::string,std::string>();284++ptr; // skip closing '"'285/**286* Unescape value. Per https://spec.torproject.org/control-spec section 2.1.1:287*288* For future-proofing, controller implementers MAY use the following289* rules to be compatible with buggy Tor implementations and with290* future ones that implement the spec as intended:291*292* Read \n \t \r and \0 ... \377 as C escapes.293* Treat a backslash followed by any other character as that character.294*/295std::string escaped_value;296for (size_t i = 0; i < value.size(); ++i) {297if (value[i] == '\\') {298// This will always be valid, because if the QuotedString299// ended in an odd number of backslashes, then the parser300// would already have returned above, due to a missing301// terminating double-quote.302++i;303if (value[i] == 'n') {304escaped_value.push_back('\n');305} else if (value[i] == 't') {306escaped_value.push_back('\t');307} else if (value[i] == 'r') {308escaped_value.push_back('\r');309} else if ('0' <= value[i] && value[i] <= '7') {310size_t j;311// Octal escape sequences have a limit of three octal digits,312// but terminate at the first character that is not a valid313// octal digit if encountered sooner.314for (j = 1; j < 3 && (i+j) < value.size() && '0' <= value[i+j] && value[i+j] <= '7'; ++j) {}315// Tor restricts first digit to 0-3 for three-digit octals.316// A leading digit of 4-7 would therefore be interpreted as317// a two-digit octal.318if (j == 3 && value[i] > '3') {319j--;320}321const auto end{i + j};322uint8_t val{0};323while (i < end) {324val *= 8;325val += value[i++] - '0';326}327escaped_value.push_back(char(val));328// Account for automatic incrementing at loop end329--i;330} else {331escaped_value.push_back(value[i]);332}333} else {334escaped_value.push_back(value[i]);335}336}337value = escaped_value;338} else { // Unquoted value. Note that values can contain '=' at will, just no spaces339while (ptr < s.size() && s[ptr] != ' ') {340value.push_back(s[ptr]);341++ptr;342}343}344if (ptr < s.size() && s[ptr] == ' ')345++ptr; // skip ' ' after key=value346mapping[key] = value;347}348return mapping;349}351TorController::TorController(const std::string& tor_control_center, const CService& target)352: m_tor_control_center(tor_control_center),353m_conn(m_interrupt),354m_reconnect(true),355m_reconnect_timeout(RECONNECT_TIMEOUT_START),356m_target(target)357{358// Read service private key if cached359std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile());360if (pkf.first) {361LogDebug(BCLog::TOR, "Reading cached private key from %s", fs::PathToString(GetPrivateKeyFile()));362m_private_key = pkf.second;363}364m_thread = std::thread(&util::TraceThread, "torcontrol", [this] { ThreadControl(); });365}367TorController::~TorController()368{369Interrupt();370Join();371if (m_service.IsValid()) {372RemoveLocal(m_service);373}374}376void TorController::Interrupt()377{378m_reconnect = false;379m_interrupt();380}382void TorController::Join()383{384if (m_thread.joinable()) {385m_thread.join();386}387}389void TorController::ThreadControl()390{391LogDebug(BCLog::TOR, "Entering Tor control thread");393while (!m_interrupt) {394LogDebug(BCLog::TOR, "Attempting to connect to Tor control port %s", m_tor_control_center);395if (m_conn.Connect(m_tor_control_center)) {396connected_cb(m_conn);397while (!m_interrupt) {398if (!m_conn.WaitForData(std::chrono::seconds(1))) {399if (m_conn.IsConnected()) continue;400LogDebug(BCLog::TOR, "Lost connection to Tor control port");401break;402}403if (!m_conn.ReceiveAndProcess()) break;404}405} else {406LogWarning("tor: Initiating connection to Tor control port %s failed", m_tor_control_center);407}408disconnected_cb(m_conn);409}410LogDebug(BCLog::TOR, "Exited Tor control thread");411}413void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlReply& reply)414{415// NOTE: We can only get here if -onion is unset416std::string socks_location;417if (reply.code == TOR_REPLY_OK) {418for (const auto& line : reply.lines) {419if (line.starts_with("net/listeners/socks=")) {420const std::string port_list_str = line.substr(20);421std::vector<std::string> port_list = SplitString(port_list_str, ' ');423for (auto& portstr : port_list) {424if (portstr.empty()) continue;425if ((portstr[0] == '"' || portstr[0] == '\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) {426portstr = portstr.substr(1, portstr.size() - 2);427if (portstr.empty()) continue;428}429socks_location = portstr;430if (portstr.starts_with("127.0.0.1:")) {431// Prefer localhost - ignore other ports432break;433}434}435}436}437if (!socks_location.empty()) {438LogDebug(BCLog::TOR, "Get SOCKS port command yielded %s", socks_location);439} else {440LogWarning("tor: Get SOCKS port command returned nothing");441}442} else if (reply.code == TOR_REPLY_UNRECOGNIZED) {443LogWarning("tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)");444} else {445LogWarning("tor: Get SOCKS port command failed; error code %d", reply.code);446}448CService resolved;449Assume(!resolved.IsValid());450if (!socks_location.empty()) {451resolved = LookupNumeric(socks_location, DEFAULT_TOR_SOCKS_PORT);452}453if (!resolved.IsValid()) {454// Fallback to old behaviour455resolved = LookupNumeric("127.0.0.1", DEFAULT_TOR_SOCKS_PORT);456}458Assume(resolved.IsValid());459LogDebug(BCLog::TOR, "Configuring onion proxy for %s", resolved.ToStringAddrPort());461// Add Tor as proxy for .onion addresses.462// Enable stream isolation to prevent connection correlation and enhance privacy, by forcing a different Tor circuit for every connection.463// For this to work, the IsolateSOCKSAuth flag must be enabled on SOCKSPort (which is the default, see the IsolateSOCKSAuth section of Tor's manual page).464Proxy addrOnion = Proxy(resolved, /*tor_stream_isolation=*/ true);465SetProxy(NET_ONION, addrOnion);467const auto onlynets = gArgs.GetArgs("-onlynet");469const bool onion_allowed_by_onlynet{470onlynets.empty() ||471std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {472return ParseNetwork(n) == NET_ONION;473})};475if (onion_allowed_by_onlynet) {476// If NET_ONION is reachable, then the below is a noop.477//478// If NET_ONION is not reachable, then none of -proxy or -onion was given.479// Since we are here, then -torcontrol and -torpassword were given.480g_reachable_nets.Add(NET_ONION);481}482}484static std::string MakeAddOnionCmd(const std::string& private_key, const std::string& target, bool enable_pow)485{486// Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports.487return strprintf("ADD_ONION %s%s Port=%i,%s",488private_key,489enable_pow ? " PoWDefensesEnabled=1" : "",490Params().GetDefaultPort(),491target);492}494void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply, bool pow_was_enabled)495{496if (reply.code == TOR_REPLY_OK) {497LogDebug(BCLog::TOR, "ADD_ONION successful (PoW defenses %s)", pow_was_enabled ? "enabled" : "disabled");498for (const std::string &s : reply.lines) {499std::map<std::string,std::string> m = ParseTorReplyMapping(s);500std::map<std::string,std::string>::iterator i;