Skip to content

Commit b042c4f

Browse files
committed
Merge bitcoin/bitcoin#31223: net, init: derive default onion port if a user specified a -port
1dd3af8 Add release note for #31223 (Martin Zumsande) 997757d test: add functional test for -port behavior (Martin Zumsande) 0e2b12b net, init: derive default onion port if a user specified a -port (Martin Zumsande) Pull request description: This resolves #31133 (setups with multiple local nodes each using a different `-port` no longer working with v28.0, see the issue description for more details) by deriving the default onion listening port to be the value specified by `-port` incremented by 1 (idea by vasild / laanwj). Note that with this fix, the chosen `-port` values of two local nodes cannot be adjacent, otherwise there will be port collisions again. From the discussion in the linked issue, this was the most popular option, followed by doing nothing and telling affected users to change their setups to use `-bind` instead of `-port`. But more opinions are certainly welcome! I think that if we decide to do something about the problem described in the issue, we should do so soon (in 28.1.), so I opened this PR. Fixes #31133 ACKs for top commit: achow101: ACK 1dd3af8 laanwj: Tested ACK 1dd3af8 tdb3: Code review ACK 1dd3af8 Tree-SHA512: 37fda2b23bbedcab5df3a401cf5afce66ae5318fb78f9660f83e3fd075b528e8156d7a0903f9a12ffe97ab5d83860587116b74af28670a1f4c2f0d1be4999f40
2 parents d73f37d + 1dd3af8 commit b042c4f

File tree

9 files changed

+94
-19
lines changed

9 files changed

+94
-19
lines changed

doc/release-notes-31223.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
P2P and network changes
2+
-----------------------
3+
When the `-port` configuration option is used, the default onion listening port will now
4+
be derived to be that port + 1 instead of being set to a fixed value (8334 on mainnet).
5+
This re-allows setups with multiple local nodes using different `-port` and not using `-bind`,
6+
which would lead to a startup failure in v28.0 due to a port collision.
7+
8+
Note that a `HiddenServicePort` manually configured in `torrc` may need adjustment if used in
9+
connection with the `-port` option.
10+
For example, if you are using `-port=5555` with a non-standard value and not using `-bind=...=onion`,
11+
previously Bitcoin Core would listen for incoming Tor connections on `127.0.0.1:8334`.
12+
Now it would listen on `127.0.0.1:5556` (`-port` plus one). If you configured the hidden service manually
13+
in torrc now you have to change it from `HiddenServicePort 8333 127.0.0.1:8334` to `HiddenServicePort 8333
14+
127.0.0.1:5556`, or configure bitcoind with `-bind=127.0.0.1:8334=onion` to get the previous behavior.
15+
(#31223)

src/chainparamsbase.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const ChainType chain)
4141
{
4242
switch (chain) {
4343
case ChainType::MAIN:
44-
return std::make_unique<CBaseChainParams>("", 8332, 8334);
44+
return std::make_unique<CBaseChainParams>("", 8332);
4545
case ChainType::TESTNET:
46-
return std::make_unique<CBaseChainParams>("testnet3", 18332, 18334);
46+
return std::make_unique<CBaseChainParams>("testnet3", 18332);
4747
case ChainType::TESTNET4:
48-
return std::make_unique<CBaseChainParams>("testnet4", 48332, 48334);
48+
return std::make_unique<CBaseChainParams>("testnet4", 48332);
4949
case ChainType::SIGNET:
50-
return std::make_unique<CBaseChainParams>("signet", 38332, 38334);
50+
return std::make_unique<CBaseChainParams>("signet", 38332);
5151
case ChainType::REGTEST:
52-
return std::make_unique<CBaseChainParams>("regtest", 18443, 18445);
52+
return std::make_unique<CBaseChainParams>("regtest", 18443);
5353
}
5454
assert(false);
5555
}

src/chainparamsbase.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@ class CBaseChainParams
2222
public:
2323
const std::string& DataDir() const { return strDataDir; }
2424
uint16_t RPCPort() const { return m_rpc_port; }
25-
uint16_t OnionServiceTargetPort() const { return m_onion_service_target_port; }
2625

2726
CBaseChainParams() = delete;
28-
CBaseChainParams(const std::string& data_dir, uint16_t rpc_port, uint16_t onion_service_target_port)
29-
: m_rpc_port(rpc_port), m_onion_service_target_port(onion_service_target_port), strDataDir(data_dir) {}
27+
CBaseChainParams(const std::string& data_dir, uint16_t rpc_port)
28+
: m_rpc_port(rpc_port), strDataDir(data_dir) {}
3029

3130
private:
3231
const uint16_t m_rpc_port;
33-
const uint16_t m_onion_service_target_port;
3432
std::string strDataDir;
3533
};
3634

src/init.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
523523
argsman.AddArg("-addnode=<ip>", strprintf("Add a node to connect to and attempt to keep the connection open (see the addnode RPC help for more info). This option can be specified multiple times to add multiple nodes; connections are limited to %u at a time and are counted separately from the -maxconnections limit.", MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
524524
argsman.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
525525
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
526-
argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), testnet4BaseParams->OnionServiceTargetPort(), signetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
526+
argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultChainParams->GetDefaultPort() + 1, testnetChainParams->GetDefaultPort() + 1, testnet4ChainParams->GetDefaultPort() + 1, signetChainParams->GetDefaultPort() + 1, regtestChainParams->GetDefaultPort() + 1), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
527527
argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
528528
argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
529529
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -550,7 +550,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
550550
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
551551
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
552552
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
553-
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).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
553+
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);
554554
#ifdef HAVE_SOCKADDR_UN
555555
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);
556556
#else
@@ -1858,6 +1858,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
18581858
const uint16_t default_bind_port =
18591859
static_cast<uint16_t>(args.GetIntArg("-port", Params().GetDefaultPort()));
18601860

1861+
const uint16_t default_bind_port_onion = default_bind_port + 1;
1862+
18611863
const auto BadPortWarning = [](const char* prefix, uint16_t port) {
18621864
return strprintf(_("%s request to listen on port %u. This port is considered \"bad\" and "
18631865
"thus it is unlikely that any peer will connect to it. See "
@@ -1882,7 +1884,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
18821884
const std::string network_type = bind_arg.substr(index + 1);
18831885
if (network_type == "onion") {
18841886
const std::string truncated_bind_arg = bind_arg.substr(0, index);
1885-
bind_addr = Lookup(truncated_bind_arg, BaseParams().OnionServiceTargetPort(), false);
1887+
bind_addr = Lookup(truncated_bind_arg, default_bind_port_onion, false);
18861888
if (bind_addr.has_value()) {
18871889
connOptions.onion_binds.push_back(bind_addr.value());
18881890
continue;
@@ -1918,7 +1920,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
19181920
} else if (!connOptions.vBinds.empty()) {
19191921
onion_service_target = connOptions.vBinds.front();
19201922
} else {
1921-
onion_service_target = DefaultOnionServiceTarget();
1923+
onion_service_target = DefaultOnionServiceTarget(default_bind_port_onion);
19221924
connOptions.onion_binds.push_back(onion_service_target);
19231925
}
19241926

src/torcontrol.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,9 +711,9 @@ void StopTorControl()
711711
}
712712
}
713713

714-
CService DefaultOnionServiceTarget()
714+
CService DefaultOnionServiceTarget(uint16_t port)
715715
{
716716
struct in_addr onion_service_target;
717717
onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
718-
return {onion_service_target, BaseParams().OnionServiceTargetPort()};
718+
return {onion_service_target, port};
719719
}

src/torcontrol.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void StartTorControl(CService onion_service_target);
2727
void InterruptTorControl();
2828
void StopTorControl();
2929

30-
CService DefaultOnionServiceTarget();
30+
CService DefaultOnionServiceTarget(uint16_t port);
3131

3232
/** Reply from Tor, can be single or multi-line */
3333
class TorControlReply

test/functional/feature_port.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2024-present The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""
6+
Test the -port option and its interactions with
7+
-bind.
8+
"""
9+
10+
from test_framework.test_framework import (
11+
BitcoinTestFramework,
12+
)
13+
from test_framework.util import (
14+
p2p_port,
15+
)
16+
17+
18+
class PortTest(BitcoinTestFramework):
19+
def set_test_params(self):
20+
self.setup_clean_chain = True
21+
# Avoid any -bind= on the command line.
22+
self.bind_to_localhost_only = False
23+
self.num_nodes = 1
24+
25+
def run_test(self):
26+
node = self.nodes[0]
27+
node.has_explicit_bind = True
28+
port1 = p2p_port(self.num_nodes)
29+
port2 = p2p_port(self.num_nodes + 5)
30+
31+
self.log.info("When starting with -port, bitcoind binds to it and uses port + 1 for an onion bind")
32+
with node.assert_debug_log(expected_msgs=[f'Bound to 0.0.0.0:{port1}', f'Bound to 127.0.0.1:{port1 + 1}']):
33+
self.restart_node(0, extra_args=["-listen", f"-port={port1}"])
34+
35+
self.log.info("When specifying -port multiple times, only the last one is taken")
36+
with node.assert_debug_log(expected_msgs=[f'Bound to 0.0.0.0:{port2}', f'Bound to 127.0.0.1:{port2 + 1}'], unexpected_msgs=[f'Bound to 0.0.0.0:{port1}']):
37+
self.restart_node(0, extra_args=["-listen", f"-port={port1}", f"-port={port2}"])
38+
39+
self.log.info("When specifying ports with both -port and -bind, the one from -port is ignored")
40+
with node.assert_debug_log(expected_msgs=[f'Bound to 0.0.0.0:{port2}'], unexpected_msgs=[f'Bound to 0.0.0.0:{port1}']):
41+
self.restart_node(0, extra_args=["-listen", f"-port={port1}", f"-bind=0.0.0.0:{port2}"])
42+
43+
self.log.info("When -bind specifies no port, the values from -port and -bind are combined")
44+
with self.nodes[0].assert_debug_log(expected_msgs=[f'Bound to 0.0.0.0:{port1}']):
45+
self.restart_node(0, extra_args=["-listen", f"-port={port1}", "-bind=0.0.0.0"])
46+
47+
self.log.info("When an onion bind specifies no port, the value from -port, incremented by 1, is taken")
48+
with self.nodes[0].assert_debug_log(expected_msgs=[f'Bound to 127.0.0.1:{port1 + 1}']):
49+
self.restart_node(0, extra_args=["-listen", f"-port={port1}", "-bind=127.0.0.1=onion"])
50+
51+
self.log.info("Invalid values for -port raise errors")
52+
self.stop_node(0)
53+
node.extra_args = ["-listen", "-port=65536"]
54+
node.assert_start_raises_init_error(expected_msg="Error: Invalid port specified in -port: '65536'")
55+
node.extra_args = ["-listen", "-port=0"]
56+
node.assert_start_raises_init_error(expected_msg="Error: Invalid port specified in -port: '0'")
57+
58+
59+
if __name__ == '__main__':
60+
PortTest(__file__).main()

test/functional/test_framework/test_node.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,9 @@ def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, env=None
221221
extra_args = self.extra_args
222222

223223
# If listening and no -bind is given, then bitcoind would bind P2P ports on
224-
# 0.0.0.0:P and 127.0.0.1:18445 (for incoming Tor connections), where P is
224+
# 0.0.0.0:P and 127.0.0.1:P+1 (for incoming Tor connections), where P is
225225
# a unique port chosen by the test framework and configured as port=P in
226-
# bitcoin.conf. To avoid collisions on 127.0.0.1:18445, change it to
227-
# 127.0.0.1:tor_port().
226+
# bitcoin.conf. To avoid collisions, change it to 127.0.0.1:tor_port().
228227
will_listen = all(e != "-nolisten" and e != "-listen=0" for e in extra_args)
229228
has_explicit_bind = self.has_explicit_bind or any(e.startswith("-bind=") for e in extra_args)
230229
if will_listen and not has_explicit_bind:

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@
341341
'feature_minchainwork.py',
342342
'rpc_estimatefee.py',
343343
'rpc_getblockstats.py',
344+
'feature_port.py',
344345
'feature_bind_port_externalip.py',
345346
'wallet_create_tx.py --legacy-wallet',
346347
'wallet_send.py --legacy-wallet',

0 commit comments

Comments
 (0)