Skip to content

Commit 157bbd0

Browse files
committed
Merge bitcoin/bitcoin#32425: config: allow setting -proxy per network
e98c51f doc: update tor.md to mention the new -proxy=addr:port=tor (Vasil Dimov) ca5781e 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: bitcoin/bitcoin#24450 ACKs for top commit: pinheadmz: ACK e98c51f caesrcd: reACK e98c51f danielabrozzoni: Code Review ACK e98c51f 1440000bytes: ACK bitcoin/bitcoin@e98c51f Tree-SHA512: 0cb590cb72b9393cc36357e8bd7861514ec4c5bc044a154e59601420b1fd6240f336ab538ed138bc769fca3d17e03725d56de382666420dc0787895d5bfec131
2 parents ebec7bf + e98c51f commit 157bbd0

File tree

5 files changed

+183
-71
lines changed

5 files changed

+183
-71
lines changed

doc/tor.md

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,53 @@ You can use the `getnodeaddresses` RPC to fetch a number of onion peers known to
3434
The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all
3535
outgoing connections, but more is possible.
3636

37-
-proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy
38-
server will be used to try to reach .onion addresses as well.
39-
You need to use -noonion or -onion=0 to explicitly disable
40-
outbound access to onion services.
41-
42-
-onion=ip:port Set the proxy server to use for Tor onion services. You do not
43-
need to set this if it's the same as -proxy. You can use -onion=0
44-
to explicitly disable access to onion services.
45-
------------------------------------------------------------------
46-
Note: Only the -proxy option sets the proxy for DNS requests;
47-
with -onion they will not route over Tor, so use -proxy if you
48-
have privacy concerns.
49-
------------------------------------------------------------------
50-
51-
-listen When using -proxy, listening is disabled by default. If you want
52-
to manually configure an onion service (see section 3), you'll
53-
need to enable it explicitly.
54-
55-
-connect=X When behind a Tor proxy, you can specify .onion addresses instead
56-
-addnode=X of IP addresses or hostnames in these parameters. It requires
57-
-seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with
58-
other P2P nodes.
59-
60-
-onlynet=onion Make automatic outbound connections only to .onion addresses.
61-
Inbound and manual connections are not affected by this option.
62-
It can be specified multiple times to allow multiple networks,
63-
e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns.
37+
-proxy=ip[:port]
38+
Set the proxy server. It will be used to try to reach .onion addresses
39+
as well. You need to use -noonion or -onion=0 to explicitly disable
40+
outbound access to onion services.
41+
42+
-proxy=ip[:port]=tor
43+
or
44+
-onion=ip[:port]
45+
Set the proxy server for reaching .onion addresses. You do not need to
46+
set this if it's the same as the generic -proxy. You can use -onion=0 to
47+
explicitly disable access to onion services.
48+
------------------------------------------------------------------------
49+
Note: The proxy for DNS requests is taken from
50+
-proxy=addr:port or
51+
-proxy=addr:port=ipv4 or
52+
-proxy=addr:port=ipv6
53+
(last one if multiple options are given). It is not taken from
54+
-proxy=addr:port=tor or
55+
-onion=addr:port.
56+
If no proxy for DNS requests is configured, then they will be done using
57+
the functions provided by the operating system, most likely resulting in
58+
them being done over the clearnet to the DNS servers of the internet
59+
service provider.
60+
------------------------------------------------------------------------
61+
62+
If -proxy or -onion is specified multiple times, later occurences override
63+
earlier ones and command line overrides the config file. UNIX domain sockets may
64+
be used for proxy connections. Set `-onion` or `-proxy` to the local socket path
65+
with the prefix `unix:` (e.g. `-onion=unix:/home/me/torsocket`).
66+
67+
-listen
68+
When using -proxy, listening is disabled by default. If you want to
69+
manually configure an onion service (see section 3), you'll need to
70+
enable it explicitly.
71+
72+
-connect=X
73+
-addnode=X
74+
-seednode=X
75+
When behind a Tor proxy, you can specify .onion addresses instead of IP
76+
addresses or hostnames in these parameters. Such addresses can also be
77+
exchanged with other P2P nodes.
78+
79+
-onlynet=onion
80+
Make automatic outbound connections only to .onion addresses. Inbound
81+
and manual connections are not affected by this option. It can be
82+
specified multiple times to allow multiple networks, e.g. onlynet=onion,
83+
onlynet=i2p, onlynet=cjdns.
6484

6585
In a typical situation, this suffices to run behind a Tor proxy:
6686

src/init.cpp

Lines changed: 94 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -549,11 +549,26 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
549549
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
550550
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
551551
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);
552+
const std::string proxy_doc_for_value =
552553
#ifdef HAVE_SOCKADDR_UN
553-
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);
554+
"<ip>[:<port>]|unix:<path>";
554555
#else
555-
argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION);
556+
"<ip>[:<port>]";
556557
#endif
558+
const std::string proxy_doc_for_unix_socket =
559+
#ifdef HAVE_SOCKADDR_UN
560+
"May be a local file path prefixed with 'unix:' if the proxy supports it. ";
561+
#else
562+
"";
563+
#endif
564+
argsman.AddArg("-proxy=" + proxy_doc_for_value + "[=<network>]",
565+
"Connect through SOCKS5 proxy, set -noproxy to disable. " +
566+
proxy_doc_for_unix_socket +
567+
"Could end in =network to set the proxy only for that network. " +
568+
"The network can be any of ipv4, ipv6, tor or cjdns. " +
569+
"(default: disabled)",
570+
ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION,
571+
OptionsCategory::CONNECTION);
557572
argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
558573
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);
559574
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) {
11621177
}
11631178
}
11641179

1165-
for ([[maybe_unused]] const auto& [arg, unix] : std::vector<std::pair<std::string, bool>>{
1166-
// arg name UNIX socket support
1167-
{"-i2psam", false},
1168-
{"-onion", true},
1169-
{"-proxy", true},
1170-
{"-rpcbind", false},
1171-
{"-torcontrol", false},
1172-
{"-whitebind", false},
1173-
{"-zmqpubhashblock", true},
1174-
{"-zmqpubhashtx", true},
1175-
{"-zmqpubrawblock", true},
1176-
{"-zmqpubrawtx", true},
1177-
{"-zmqpubsequence", true},
1180+
for ([[maybe_unused]] const auto& [param_name, unix, suffix_allowed] : std::vector<std::tuple<std::string, bool, bool>>{
1181+
// arg name UNIX socket support =suffix allowed
1182+
{"-i2psam", false, false},
1183+
{"-onion", true, false},
1184+
{"-proxy", true, true},
1185+
{"-bind", false, true},
1186+
{"-rpcbind", false, false},
1187+
{"-torcontrol", false, false},
1188+
{"-whitebind", false, false},
1189+
{"-zmqpubhashblock", true, false},
1190+
{"-zmqpubhashtx", true, false},
1191+
{"-zmqpubrawblock", true, false},
1192+
{"-zmqpubrawtx", true, false},
1193+
{"-zmqpubsequence", true, false},
11781194
}) {
1179-
for (const std::string& socket_addr : args.GetArgs(arg)) {
1195+
for (const std::string& param_value : args.GetArgs(param_name)) {
1196+
const std::string param_value_hostport{
1197+
suffix_allowed ? param_value.substr(0, param_value.rfind('=')) : param_value};
11801198
std::string host_out;
11811199
uint16_t port_out{0};
1182-
if (!SplitHostPort(socket_addr, port_out, host_out)) {
1200+
if (!SplitHostPort(param_value_hostport, port_out, host_out)) {
11831201
#ifdef HAVE_SOCKADDR_UN
11841202
// Allow unix domain sockets for some options e.g. unix:/some/file/path
1185-
if (!unix || !socket_addr.starts_with(ADDR_PREFIX_UNIX)) {
1186-
return InitError(InvalidPortErrMsg(arg, socket_addr));
1203+
if (!unix || !param_value.starts_with(ADDR_PREFIX_UNIX)) {
1204+
return InitError(InvalidPortErrMsg(param_name, param_value));
11871205
}
11881206
#else
1189-
return InitError(InvalidPortErrMsg(arg, socket_addr));
1207+
return InitError(InvalidPortErrMsg(param_name, param_value));
11901208
#endif
11911209
}
11921210
}
@@ -1544,33 +1562,66 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15441562
// Check for host lookup allowed before parsing any network related parameters
15451563
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
15461564

1547-
Proxy onion_proxy;
1548-
15491565
bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
1550-
// -proxy sets a proxy for all outgoing network traffic
1551-
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
1552-
std::string proxyArg = args.GetArg("-proxy", "");
1553-
if (proxyArg != "" && proxyArg != "0") {
1554-
Proxy addrProxy;
1555-
if (IsUnixSocketPath(proxyArg)) {
1556-
addrProxy = Proxy(proxyArg, /*tor_stream_isolation=*/proxyRandomize);
1557-
} else {
1558-
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
1559-
if (!proxyAddr.has_value()) {
1560-
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
1566+
// -proxy sets a proxy for outgoing network traffic, possibly per network.
1567+
// -noproxy, -proxy=0 or -proxy="" can be used to remove the proxy setting, this is the default
1568+
Proxy ipv4_proxy;
1569+
Proxy ipv6_proxy;
1570+
Proxy onion_proxy;
1571+
Proxy name_proxy;
1572+
Proxy cjdns_proxy;
1573+
for (const std::string& param_value : args.GetArgs("-proxy")) {
1574+
const auto eq_pos{param_value.rfind('=')};
1575+
const std::string proxy_str{param_value.substr(0, eq_pos)}; // e.g. 127.0.0.1:9050=ipv4 -> 127.0.0.1:9050
1576+
std::string net_str;
1577+
if (eq_pos != std::string::npos) {
1578+
if (eq_pos + 1 == param_value.length()) {
1579+
return InitError(strprintf(_("Invalid -proxy address or hostname, ends with '=': '%s'"), param_value));
15611580
}
1562-
1563-
addrProxy = Proxy(proxyAddr.value(), /*tor_stream_isolation=*/proxyRandomize);
1581+
net_str = ToLower(param_value.substr(eq_pos + 1)); // e.g. 127.0.0.1:9050=ipv4 -> ipv4
15641582
}
15651583

1566-
if (!addrProxy.IsValid())
1567-
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
1584+
Proxy proxy;
1585+
if (!proxy_str.empty() && proxy_str != "0") {
1586+
if (IsUnixSocketPath(proxy_str)) {
1587+
proxy = Proxy{proxy_str, /*tor_stream_isolation=*/proxyRandomize};
1588+
} else {
1589+
const std::optional<CService> addr{Lookup(proxy_str, DEFAULT_TOR_SOCKS_PORT, fNameLookup)};
1590+
if (!addr.has_value()) {
1591+
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxy_str));
1592+
}
1593+
proxy = Proxy{addr.value(), /*tor_stream_isolation=*/proxyRandomize};
1594+
}
1595+
if (!proxy.IsValid()) {
1596+
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxy_str));
1597+
}
1598+
}
15681599

1569-
SetProxy(NET_IPV4, addrProxy);
1570-
SetProxy(NET_IPV6, addrProxy);
1571-
SetProxy(NET_CJDNS, addrProxy);
1572-
SetNameProxy(addrProxy);
1573-
onion_proxy = addrProxy;
1600+
if (net_str.empty()) { // For all networks.
1601+
ipv4_proxy = ipv6_proxy = name_proxy = cjdns_proxy = onion_proxy = proxy;
1602+
} else if (net_str == "ipv4") {
1603+
ipv4_proxy = name_proxy = proxy;
1604+
} else if (net_str == "ipv6") {
1605+
ipv6_proxy = name_proxy = proxy;
1606+
} else if (net_str == "tor" || net_str == "onion") {
1607+
onion_proxy = proxy;
1608+
} else if (net_str == "cjdns") {
1609+
cjdns_proxy = proxy;
1610+
} else {
1611+
return InitError(strprintf(_("Unrecognized network in -proxy='%s': '%s'"), param_value, net_str));
1612+
}
1613+
}
1614+
if (ipv4_proxy.IsValid()) {
1615+
SetProxy(NET_IPV4, ipv4_proxy);
1616+
}
1617+
if (ipv6_proxy.IsValid()) {
1618+
SetProxy(NET_IPV6, ipv6_proxy);
1619+
}
1620+
if (name_proxy.IsValid()) {
1621+
SetNameProxy(name_proxy);
1622+
}
1623+
if (cjdns_proxy.IsValid()) {
1624+
SetProxy(NET_CJDNS, cjdns_proxy);
15741625
}
15751626

15761627
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)
15911642
if (IsUnixSocketPath(onionArg)) {
15921643
onion_proxy = Proxy(onionArg, /*tor_stream_isolation=*/proxyRandomize);
15931644
} else {
1594-
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
1645+
const std::optional<CService> addr{Lookup(onionArg, DEFAULT_TOR_SOCKS_PORT, fNameLookup)};
15951646
if (!addr.has_value() || !addr->IsValid()) {
15961647
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
15971648
}

src/torcontrol.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ static const float RECONNECT_TIMEOUT_MAX = 600.0;
7171
* this is belt-and-suspenders sanity limit to prevent memory exhaustion.
7272
*/
7373
static const int MAX_LINE_LENGTH = 100000;
74-
static const uint16_t DEFAULT_TOR_SOCKS_PORT = 9050;
7574

7675
/****** Low-level TorControlConnection ********/
7776

src/torcontrol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020
#include <vector>
2121

22+
constexpr uint16_t DEFAULT_TOR_SOCKS_PORT{9050};
2223
constexpr int DEFAULT_TOR_CONTROL_PORT = 9051;
2324
extern const std::string DEFAULT_TOR_CONTROL;
2425
static const bool DEFAULT_LISTEN_ONION = true;

test/functional/feature_proxy.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,47 @@ def networks_dict(d):
444444
msg = "Error: Unknown network specified in -onlynet: 'abc'"
445445
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
446446

447+
self.log.info("Test passing trailing '=' raises expected init error")
448+
self.nodes[1].extra_args = ["-proxy=127.0.0.1:9050="]
449+
msg = "Error: Invalid -proxy address or hostname, ends with '=': '127.0.0.1:9050='"
450+
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
451+
452+
self.log.info("Test passing unrecognized network raises expected init error")
453+
self.nodes[1].extra_args = ["-proxy=127.0.0.1:9050=foo"]
454+
msg = "Error: Unrecognized network in -proxy='127.0.0.1:9050=foo': 'foo'"
455+
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
456+
457+
self.log.info("Test passing proxy only for IPv6")
458+
self.start_node(1, extra_args=["-proxy=127.6.6.6:6666=ipv6"])
459+
nets = networks_dict(self.nodes[1].getnetworkinfo())
460+
assert_equal(nets["ipv4"]["proxy"], "")
461+
assert_equal(nets["ipv6"]["proxy"], "127.6.6.6:6666")
462+
self.stop_node(1)
463+
464+
self.log.info("Test passing separate proxy for IPv4 and IPv6")
465+
self.start_node(1, extra_args=["-proxy=127.4.4.4:4444=ipv4", "-proxy=127.6.6.6:6666=ipv6"])
466+
nets = networks_dict(self.nodes[1].getnetworkinfo())
467+
assert_equal(nets["ipv4"]["proxy"], "127.4.4.4:4444")
468+
assert_equal(nets["ipv6"]["proxy"], "127.6.6.6:6666")
469+
self.stop_node(1)
470+
471+
self.log.info("Test overriding the Tor proxy")
472+
self.start_node(1, extra_args=["-proxy=127.1.1.1:1111", "-proxy=127.2.2.2:2222=tor"])
473+
nets = networks_dict(self.nodes[1].getnetworkinfo())
474+
assert_equal(nets["ipv4"]["proxy"], "127.1.1.1:1111")
475+
assert_equal(nets["ipv6"]["proxy"], "127.1.1.1:1111")
476+
assert_equal(nets["onion"]["proxy"], "127.2.2.2:2222")
477+
self.stop_node(1)
478+
479+
self.log.info("Test removing CJDNS proxy")
480+
self.start_node(1, extra_args=["-proxy=127.1.1.1:1111", "-proxy=0=cjdns"])
481+
nets = networks_dict(self.nodes[1].getnetworkinfo())
482+
assert_equal(nets["ipv4"]["proxy"], "127.1.1.1:1111")
483+
assert_equal(nets["ipv6"]["proxy"], "127.1.1.1:1111")
484+
assert_equal(nets["onion"]["proxy"], "127.1.1.1:1111")
485+
assert_equal(nets["cjdns"]["proxy"], "")
486+
self.stop_node(1)
487+
447488
self.log.info("Test passing too-long unix path to -proxy raises init error")
448489
self.nodes[1].extra_args = [f"-proxy=unix:{'x' * 1000}"]
449490
if self.have_unix_sockets:

0 commit comments

Comments
 (0)