Merge bitcoin/bitcoin#32425: config: allow setting -proxy per network

e98c51fcce doc: update tor.md to mention the new -proxy=addr:port=tor (Vasil Dimov)
ca5781e23a config: allow setting -proxy per network (Vasil Dimov)

Pull request description:

  `-proxy=addr:port` specifies the proxy for all networks (except I2P). Previously only the Tor proxy could have been specified separately via `-onion=addr:port`.

  Make it possible to specify separately the proxy for IPv4, IPv6, Tor and CJDNS by e.g. `-proxy=addr:port=ipv6`. Or remove the proxy for a given network, e.g. `-proxy=0=cjdns`.

  Resolves: https://github.com/bitcoin/bitcoin/issues/24450

ACKs for top commit:
  pinheadmz:
    ACK e98c51fcce
  caesrcd:
    reACK e98c51fcce
  danielabrozzoni:
    Code Review ACK e98c51fcce
  1440000bytes:
    ACK https://github.com/bitcoin/bitcoin/pull/32425/commits/e98c51fcce9ae3f441a416cab32a5c85756c6c64

Tree-SHA512: 0cb590cb72b9393cc36357e8bd7861514ec4c5bc044a154e59601420b1fd6240f336ab538ed138bc769fca3d17e03725d56de382666420dc0787895d5bfec131
This commit is contained in:
merge-script committed 2025-06-10 15:57:09 -04:00
commit 157bbd0a07
5 files changed
+179 -67

No files matched your search

+43 -23
View File
@@ -34,33 +34,53 @@ You can use the `getnodeaddresses` RPC to fetch a number of onion peers known to
The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all
outgoing connections, but more is possible. outgoing connections, but more is possible.
-proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy -proxy=ip[:port]
server will be used to try to reach .onion addresses as well. Set the proxy server. It will be used to try to reach .onion addresses
You need to use -noonion or -onion=0 to explicitly disable as well. You need to use -noonion or -onion=0 to explicitly disable
outbound access to onion services. outbound access to onion services.
-onion=ip:port Set the proxy server to use for Tor onion services. You do not -proxy=ip[:port]=tor
need to set this if it's the same as -proxy. You can use -onion=0 or
to explicitly disable access to onion services. -onion=ip[:port]
------------------------------------------------------------------ Set the proxy server for reaching .onion addresses. You do not need to
Note: Only the -proxy option sets the proxy for DNS requests; set this if it's the same as the generic -proxy. You can use -onion=0 to
with -onion they will not route over Tor, so use -proxy if you explicitly disable access to onion services.
have privacy concerns. ------------------------------------------------------------------------
------------------------------------------------------------------ Note: The proxy for DNS requests is taken from
-proxy=addr:port or
-proxy=addr:port=ipv4 or
-proxy=addr:port=ipv6
(last one if multiple options are given). It is not taken from
-proxy=addr:port=tor or
-onion=addr:port.
If no proxy for DNS requests is configured, then they will be done using
the functions provided by the operating system, most likely resulting in
them being done over the clearnet to the DNS servers of the internet
service provider.
------------------------------------------------------------------------
-listen When using -proxy, listening is disabled by default. If you want If -proxy or -onion is specified multiple times, later occurences override
to manually configure an onion service (see section 3), you'll earlier ones and command line overrides the config file. UNIX domain sockets may
need to enable it explicitly. be used for proxy connections. Set `-onion` or `-proxy` to the local socket path
with the prefix `unix:` (e.g. `-onion=unix:/home/me/torsocket`).
-connect=X When behind a Tor proxy, you can specify .onion addresses instead -listen
-addnode=X of IP addresses or hostnames in these parameters. It requires When using -proxy, listening is disabled by default. If you want to
-seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with manually configure an onion service (see section 3), you'll need to
other P2P nodes. enable it explicitly.
-onlynet=onion Make automatic outbound connections only to .onion addresses. -connect=X
Inbound and manual connections are not affected by this option. -addnode=X
It can be specified multiple times to allow multiple networks, -seednode=X
e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns. When behind a Tor proxy, you can specify .onion addresses instead of IP
addresses or hostnames in these parameters. Such addresses can also be
exchanged with other P2P nodes.
-onlynet=onion
Make automatic outbound connections only to .onion addresses. Inbound
and manual connections are not affected by this option. It can be
specified multiple times to allow multiple networks, e.g. onlynet=onion,
onlynet=i2p, onlynet=cjdns.
In a typical situation, this suffices to run behind a Tor proxy: In a typical situation, this suffices to run behind a Tor proxy:
+94 -43
View File
@@ -549,11 +549,26 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md). If set to a value x, the default onion listening port will be set to x+1.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md). If set to a value x, the default onion listening port will be set to x+1.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
const std::string proxy_doc_for_value =
#ifdef HAVE_SOCKADDR_UN #ifdef HAVE_SOCKADDR_UN
argsman.AddArg("-proxy=<ip:port|path>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled). May be a local file path prefixed with 'unix:' if the proxy supports it.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); "<ip>[:<port>]|unix:<path>";
#else #else
argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); "<ip>[:<port>]";
#endif #endif
const std::string proxy_doc_for_unix_socket =
#ifdef HAVE_SOCKADDR_UN
"May be a local file path prefixed with 'unix:' if the proxy supports it. ";
#else
"";
#endif
argsman.AddArg("-proxy=" + proxy_doc_for_value + "[=<network>]",
"Connect through SOCKS5 proxy, set -noproxy to disable. " +
proxy_doc_for_unix_socket +
"Could end in =network to set the proxy only for that network. " +
"The network can be any of ipv4, ipv6, tor or cjdns. " +
"(default: disabled)",
ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION,
OptionsCategory::CONNECTION);
argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes. During startup, seednodes will be tried before dnsseeds.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes. During startup, seednodes will be tried before dnsseeds.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -1162,31 +1177,34 @@ bool CheckHostPortOptions(const ArgsManager& args) {
} }
} }
for ([[maybe_unused]] const auto& [arg, unix] : std::vector<std::pair<std::string, bool>>{ for ([[maybe_unused]] const auto& [param_name, unix, suffix_allowed] : std::vector<std::tuple<std::string, bool, bool>>{
// arg name UNIX socket support // arg name UNIX socket support =suffix allowed
{"-i2psam", false}, {"-i2psam", false, false},
{"-onion", true}, {"-onion", true, false},
{"-proxy", true}, {"-proxy", true, true},
{"-rpcbind", false}, {"-bind", false, true},
{"-torcontrol", false}, {"-rpcbind", false, false},
{"-whitebind", false}, {"-torcontrol", false, false},
{"-zmqpubhashblock", true}, {"-whitebind", false, false},
{"-zmqpubhashtx", true}, {"-zmqpubhashblock", true, false},
{"-zmqpubrawblock", true}, {"-zmqpubhashtx", true, false},
{"-zmqpubrawtx", true}, {"-zmqpubrawblock", true, false},
{"-zmqpubsequence", true}, {"-zmqpubrawtx", true, false},
{"-zmqpubsequence", true, false},
}) { }) {
for (const std::string& socket_addr : args.GetArgs(arg)) { for (const std::string& param_value : args.GetArgs(param_name)) {
const std::string param_value_hostport{
suffix_allowed ? param_value.substr(0, param_value.rfind('=')) : param_value};
std::string host_out; std::string host_out;
uint16_t port_out{0}; uint16_t port_out{0};
if (!SplitHostPort(socket_addr, port_out, host_out)) { if (!SplitHostPort(param_value_hostport, port_out, host_out)) {
#ifdef HAVE_SOCKADDR_UN #ifdef HAVE_SOCKADDR_UN
// Allow unix domain sockets for some options e.g. unix:/some/file/path // Allow unix domain sockets for some options e.g. unix:/some/file/path
if (!unix || !socket_addr.starts_with(ADDR_PREFIX_UNIX)) { if (!unix || !param_value.starts_with(ADDR_PREFIX_UNIX)) {
return InitError(InvalidPortErrMsg(arg, socket_addr)); return InitError(InvalidPortErrMsg(param_name, param_value));
} }
#else #else
return InitError(InvalidPortErrMsg(arg, socket_addr)); return InitError(InvalidPortErrMsg(param_name, param_value));
#endif #endif
} }
} }
@@ -1544,33 +1562,66 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Check for host lookup allowed before parsing any network related parameters // Check for host lookup allowed before parsing any network related parameters
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
Proxy onion_proxy;
bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE); bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
// -proxy sets a proxy for all outgoing network traffic // -proxy sets a proxy for outgoing network traffic, possibly per network.
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default // -noproxy, -proxy=0 or -proxy="" can be used to remove the proxy setting, this is the default
std::string proxyArg = args.GetArg("-proxy", ""); Proxy ipv4_proxy;
if (proxyArg != "" && proxyArg != "0") { Proxy ipv6_proxy;
Proxy addrProxy; Proxy onion_proxy;
if (IsUnixSocketPath(proxyArg)) { Proxy name_proxy;
addrProxy = Proxy(proxyArg, /*tor_stream_isolation=*/proxyRandomize); Proxy cjdns_proxy;
} else { for (const std::string& param_value : args.GetArgs("-proxy")) {
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)}; const auto eq_pos{param_value.rfind('=')};
if (!proxyAddr.has_value()) { const std::string proxy_str{param_value.substr(0, eq_pos)}; // e.g. 127.0.0.1:9050=ipv4 -> 127.0.0.1:9050
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); std::string net_str;
if (eq_pos != std::string::npos) {
if (eq_pos + 1 == param_value.length()) {
return InitError(strprintf(_("Invalid -proxy address or hostname, ends with '=': '%s'"), param_value));
} }
net_str = ToLower(param_value.substr(eq_pos + 1)); // e.g. 127.0.0.1:9050=ipv4 -> ipv4
addrProxy = Proxy(proxyAddr.value(), /*tor_stream_isolation=*/proxyRandomize);
} }
if (!addrProxy.IsValid()) Proxy proxy;
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); if (!proxy_str.empty() && proxy_str != "0") {
if (IsUnixSocketPath(proxy_str)) {
proxy = Proxy{proxy_str, /*tor_stream_isolation=*/proxyRandomize};
} else {
const std::optional<CService> addr{Lookup(proxy_str, DEFAULT_TOR_SOCKS_PORT, fNameLookup)};
if (!addr.has_value()) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxy_str));
}
proxy = Proxy{addr.value(), /*tor_stream_isolation=*/proxyRandomize};
}
if (!proxy.IsValid()) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxy_str));
}
}
SetProxy(NET_IPV4, addrProxy); if (net_str.empty()) { // For all networks.
SetProxy(NET_IPV6, addrProxy); ipv4_proxy = ipv6_proxy = name_proxy = cjdns_proxy = onion_proxy = proxy;
SetProxy(NET_CJDNS, addrProxy); } else if (net_str == "ipv4") {
SetNameProxy(addrProxy); ipv4_proxy = name_proxy = proxy;
onion_proxy = addrProxy; } else if (net_str == "ipv6") {
ipv6_proxy = name_proxy = proxy;
} else if (net_str == "tor" || net_str == "onion") {
onion_proxy = proxy;
} else if (net_str == "cjdns") {
cjdns_proxy = proxy;
} else {
return InitError(strprintf(_("Unrecognized network in -proxy='%s': '%s'"), param_value, net_str));
}
}
if (ipv4_proxy.IsValid()) {
SetProxy(NET_IPV4, ipv4_proxy);
}
if (ipv6_proxy.IsValid()) {
SetProxy(NET_IPV6, ipv6_proxy);
}
if (name_proxy.IsValid()) {
SetNameProxy(name_proxy);
}
if (cjdns_proxy.IsValid()) {
SetProxy(NET_CJDNS, cjdns_proxy);
} }
const bool onlynet_used_with_onion{!onlynets.empty() && g_reachable_nets.Contains(NET_ONION)}; const bool onlynet_used_with_onion{!onlynets.empty() && g_reachable_nets.Contains(NET_ONION)};
@@ -1591,7 +1642,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (IsUnixSocketPath(onionArg)) { if (IsUnixSocketPath(onionArg)) {
onion_proxy = Proxy(onionArg, /*tor_stream_isolation=*/proxyRandomize); onion_proxy = Proxy(onionArg, /*tor_stream_isolation=*/proxyRandomize);
} else { } else {
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)}; const std::optional<CService> addr{Lookup(onionArg, DEFAULT_TOR_SOCKS_PORT, fNameLookup)};
if (!addr.has_value() || !addr->IsValid()) { if (!addr.has_value() || !addr->IsValid()) {
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
} }
-1
View File
@@ -71,7 +71,6 @@ static const float RECONNECT_TIMEOUT_MAX = 600.0;
* this is belt-and-suspenders sanity limit to prevent memory exhaustion. * this is belt-and-suspenders sanity limit to prevent memory exhaustion.
*/ */
static const int MAX_LINE_LENGTH = 100000; static const int MAX_LINE_LENGTH = 100000;
static const uint16_t DEFAULT_TOR_SOCKS_PORT = 9050;
/****** Low-level TorControlConnection ********/ /****** Low-level TorControlConnection ********/
+1
View File
@@ -19,6 +19,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
constexpr uint16_t DEFAULT_TOR_SOCKS_PORT{9050};
constexpr int DEFAULT_TOR_CONTROL_PORT = 9051; constexpr int DEFAULT_TOR_CONTROL_PORT = 9051;
extern const std::string DEFAULT_TOR_CONTROL; extern const std::string DEFAULT_TOR_CONTROL;
static const bool DEFAULT_LISTEN_ONION = true; static const bool DEFAULT_LISTEN_ONION = true;
+41
View File
@@ -444,6 +444,47 @@ class ProxyTest(BitcoinTestFramework):
msg = "Error: Unknown network specified in -onlynet: 'abc'" msg = "Error: Unknown network specified in -onlynet: 'abc'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg) self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
self.log.info("Test passing trailing '=' raises expected init error")
self.nodes[1].extra_args = ["-proxy=127.0.0.1:9050="]
msg = "Error: Invalid -proxy address or hostname, ends with '=': '127.0.0.1:9050='"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
self.log.info("Test passing unrecognized network raises expected init error")
self.nodes[1].extra_args = ["-proxy=127.0.0.1:9050=foo"]
msg = "Error: Unrecognized network in -proxy='127.0.0.1:9050=foo': 'foo'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
self.log.info("Test passing proxy only for IPv6")
self.start_node(1, extra_args=["-proxy=127.6.6.6:6666=ipv6"])
nets = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(nets["ipv4"]["proxy"], "")
assert_equal(nets["ipv6"]["proxy"], "127.6.6.6:6666")
self.stop_node(1)
self.log.info("Test passing separate proxy for IPv4 and IPv6")
self.start_node(1, extra_args=["-proxy=127.4.4.4:4444=ipv4", "-proxy=127.6.6.6:6666=ipv6"])
nets = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(nets["ipv4"]["proxy"], "127.4.4.4:4444")
assert_equal(nets["ipv6"]["proxy"], "127.6.6.6:6666")
self.stop_node(1)
self.log.info("Test overriding the Tor proxy")
self.start_node(1, extra_args=["-proxy=127.1.1.1:1111", "-proxy=127.2.2.2:2222=tor"])
nets = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(nets["ipv4"]["proxy"], "127.1.1.1:1111")
assert_equal(nets["ipv6"]["proxy"], "127.1.1.1:1111")
assert_equal(nets["onion"]["proxy"], "127.2.2.2:2222")
self.stop_node(1)
self.log.info("Test removing CJDNS proxy")
self.start_node(1, extra_args=["-proxy=127.1.1.1:1111", "-proxy=0=cjdns"])
nets = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(nets["ipv4"]["proxy"], "127.1.1.1:1111")
assert_equal(nets["ipv6"]["proxy"], "127.1.1.1:1111")
assert_equal(nets["onion"]["proxy"], "127.1.1.1:1111")
assert_equal(nets["cjdns"]["proxy"], "")
self.stop_node(1)
self.log.info("Test passing too-long unix path to -proxy raises init error") self.log.info("Test passing too-long unix path to -proxy raises init error")
self.nodes[1].extra_args = [f"-proxy=unix:{'x' * 1000}"] self.nodes[1].extra_args = [f"-proxy=unix:{'x' * 1000}"]
if self.have_unix_sockets: if self.have_unix_sockets: