diff --git a/src/netbase.cpp b/src/netbase.cpp index 61e4c8eb3e2..1edcd694557 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -725,6 +725,43 @@ bool IsProxy(const CNetAddr &addr) { return false; } +/** + * Generate unique credentials for Tor stream isolation. Tor will create + * separate circuits for SOCKS5 proxy connections with different credentials, which + * makes it harder to correlate the connections. + */ +class TorStreamIsolationCredentialsGenerator +{ +public: + TorStreamIsolationCredentialsGenerator(): + m_prefix(GenerateUniquePrefix()) { + } + + /** Return the next unique proxy credentials. */ + ProxyCredentials Generate() { + ProxyCredentials auth; + auth.username = auth.password = strprintf("%s%i", m_prefix, m_counter); + ++m_counter; + return auth; + } + + /** Size of session prefix in bytes. */ + static constexpr size_t PREFIX_BYTE_LENGTH = 8; +private: + const std::string m_prefix; + std::atomic m_counter; + + /** Generate a random prefix for each of the credentials returned by this generator. + * This makes sure that different launches of the application (either successively or in parallel) + * will not share the same circuits, as would be the case with a bare counter. + */ + static std::string GenerateUniquePrefix() { + std::array prefix_bytes; + GetRandBytes(prefix_bytes); + return HexStr(prefix_bytes) + "-"; + } +}; + std::unique_ptr ConnectThroughProxy(const Proxy& proxy, const std::string& dest, uint16_t port, @@ -739,9 +776,8 @@ std::unique_ptr ConnectThroughProxy(const Proxy& proxy, // do socks negotiation if (proxy.m_tor_stream_isolation) { - ProxyCredentials random_auth; - static std::atomic_int counter(0); - random_auth.username = random_auth.password = strprintf("%i", counter++); + static TorStreamIsolationCredentialsGenerator generator; + ProxyCredentials random_auth{generator.Generate()}; if (!Socks5(dest, port, &random_auth, *sock)) { return {}; } diff --git a/src/test/fuzz/i2p.cpp b/src/test/fuzz/i2p.cpp index b8024f7b1c4..29a11123d9a 100644 --- a/src/test/fuzz/i2p.cpp +++ b/src/test/fuzz/i2p.cpp @@ -34,7 +34,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p) const fs::path private_key_path = gArgs.GetDataDirNet() / "fuzzed_i2p_private_key"; const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), 7656}; - const Proxy sam_proxy{addr, false}; + const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false}; CThreadInterrupt interrupt; i2p::sam::Session session{private_key_path, sam_proxy, &interrupt}; diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp index bb9ca88019c..bdb3408b66c 100644 --- a/src/test/i2p_tests.cpp +++ b/src/test/i2p_tests.cpp @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv) CThreadInterrupt interrupt; const std::optional addr{Lookup("127.0.0.1", 9000, false)}; - const Proxy sam_proxy(addr.value(), false); + const Proxy sam_proxy(addr.value(), /*tor_stream_isolation=*/false); i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt); { @@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE(listen_ok_accept_fail) CThreadInterrupt interrupt; const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656}; - const Proxy sam_proxy(addr, false); + const Proxy sam_proxy(addr, /*tor_stream_isolation=*/false); i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt); @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(damaged_private_key) CThreadInterrupt interrupt; const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656}; - const Proxy sam_proxy{addr, false}; + const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false}; i2p::sam::Session session(i2p_private_key_file, sam_proxy, &interrupt); {