Skip to content

Commit 443e2cc

Browse files
achow101vijaydasmp
authored andcommitted
Merge bitcoin#22729: Make it possible to disable Tor binds and abort startup on bind failure
bca346a net: require P2P binds to succeed (Vasil Dimov) af55253 net: report an error if unable to bind on the Tor listening addr:port (Vasil Dimov) 9a7e5f4 net: don't extra bind for Tor if binds are restricted (Vasil Dimov) Pull request description: Make it possible to disable the Tor binding on `127.0.0.1:8334` and stop startup if any P2P bind fails instead of "if all P2P binds fail". Fixes bitcoin#22726 Fixes bitcoin#22727 ACKs for top commit: achow101: ACK bca346a cbergqvist: ACK bca346a pinheadmz: ACK bca346a Tree-SHA512: fabef89a957191eea4f3e3b6109d2b8389f27ecc74440a920b0c10f31fff00a85bcfd1eb3c91826c7169c618f4de8a8d0a260e2caf40fd854f07ea9a980d8603
1 parent f5b4561 commit 443e2cc

6 files changed

Lines changed: 59 additions & 21 deletions

File tree

src/init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,6 +2697,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
26972697
CService onion_service_target;
26982698
if (!connOptions.onion_binds.empty()) {
26992699
onion_service_target = connOptions.onion_binds.front();
2700+
} else if (!connOptions.vBinds.empty()) {
2701+
onion_service_target = connOptions.vBinds.front();
27002702
} else {
27012703
onion_service_target = DefaultOnionServiceTarget();
27022704
connOptions.onion_binds.push_back(onion_service_target);

src/net.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3942,24 +3942,36 @@ bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlag
39423942

39433943
bool CConnman::InitBinds(const Options& options)
39443944
{
3945-
bool fBound = false;
39463945
for (const auto& addrBind : options.vBinds) {
3947-
fBound |= Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None);
3946+
if (!Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None)) {
3947+
return false;
3948+
}
39483949
}
39493950
for (const auto& addrBind : options.vWhiteBinds) {
3950-
fBound |= Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags);
3951+
if (!Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags)) {
3952+
return false;
3953+
}
39513954
}
39523955
for (const auto& addr_bind : options.onion_binds) {
3953-
fBound |= Bind(addr_bind, BF_DONT_ADVERTISE, NetPermissionFlags::None);
3956+
if (!Bind(addr_bind, BF_REPORT_ERROR | BF_DONT_ADVERTISE, NetPermissionFlags::None)) {
3957+
return false;
3958+
}
39543959
}
39553960
if (options.bind_on_any) {
3961+
// Don't consider errors to bind on IPv6 "::" fatal because the host OS
3962+
// may not have IPv6 support and the user did not explicitly ask us to
3963+
// bind on that.
3964+
const CService ipv6_any{in6_addr(IN6ADDR_ANY_INIT), GetListenPort()}; // ::
3965+
Bind(ipv6_any, BF_NONE, NetPermissionFlags::None);
3966+
39563967
struct in_addr inaddr_any;
39573968
inaddr_any.s_addr = htonl(INADDR_ANY);
3958-
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
3959-
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::None);
3960-
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::None);
3969+
const CService ipv4_any{inaddr_any, GetListenPort()}; // 0.0.0.0
3970+
if (!Bind(ipv4_any, BF_REPORT_ERROR, NetPermissionFlags::None)) {
3971+
return false;
3972+
}
39613973
}
3962-
return fBound;
3974+
return true;
39633975
}
39643976

39653977
bool CConnman::Start(CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync,

test/functional/feature_bind_extra.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def set_test_params(self):
3030
# Avoid any -bind= on the command line. Force the framework to avoid
3131
# adding -bind=127.0.0.1.
3232
self.bind_to_localhost_only = False
33-
self.num_nodes = 2
33+
self.num_nodes = 3
3434

3535
def setup_network(self):
3636
# Due to OS-specific network stats queries, we only run on Linux.
@@ -64,14 +64,21 @@ def setup_network(self):
6464
)
6565
port += 2
6666

67+
# Node2, no -bind=...=onion, thus no extra port for Tor target.
68+
self.expected.append(
69+
[
70+
[f"-bind=127.0.0.1:{port}"],
71+
[(loopback_ipv4, port)]
72+
],
73+
)
74+
port += 1
75+
6776
self.extra_args = list(map(lambda e: e[0], self.expected))
68-
self.add_nodes(self.num_nodes, self.extra_args)
69-
# Don't start the nodes, as some of them would collide trying to bind on the same port.
77+
self.setup_nodes()
7078

7179
def run_test(self):
72-
for i in range(len(self.expected)):
73-
self.log.info(f"Starting node {i} with {self.expected[i][0]}")
74-
self.start_node(i)
80+
for i, (args, expected_services) in enumerate(self.expected):
81+
self.log.info(f"Checking listening ports of node {i} with {args}")
7582
pid = self.nodes[i].process.pid
7683
binds = set(get_bind_addrs(pid))
7784
# Remove IPv6 addresses because on some CI environments "::1" is not configured
@@ -82,9 +89,7 @@ def run_test(self):
8289
binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds))
8390
# Remove RPC ports. They are not relevant for this test.
8491
binds = set(filter(lambda e: e[1] != rpc_port(i), binds))
85-
assert_equal(binds, set(self.expected[i][1]))
86-
self.stop_node(i)
87-
self.log.info(f"Stopped node {i}")
92+
assert_equal(binds, set(expected_services))
8893

8994
if __name__ == '__main__':
9095
BindExtraTest().main()

test/functional/test-shell.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ can be called after the TestShell is shut down.
169169

170170
| Test parameter key | Default Value | Description |
171171
|---|---|---|
172-
| `bind_to_localhost_only` | `True` | Binds bitcoind RPC services to `127.0.0.1` if set to `True`.|
172+
| `bind_to_localhost_only` | `True` | Binds bitcoind P2P services to `127.0.0.1` if set to `True`.|
173173
| `cachedir` | `"/path/to/bitcoin/test/cache"` | Sets the bitcoind datadir directory. |
174174
| `chain` | `"regtest"` | Sets the chain-type for the underlying test bitcoind processes. |
175175
| `configfile` | `"/path/to/bitcoin/test/config.ini"` | Sets the location of the test framework config file. |

test/functional/test_framework/test_node.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
wait_until_helper,
4141
p2p_port,
4242
get_chain_folder,
43+
tor_port,
4344
)
4445

4546
BITCOIND_PROC_WAIT_TIMEOUT = 60
@@ -90,8 +91,11 @@ def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timew
9091
self.cwd = cwd
9192
self.mocktime = mocktime
9293
self.descriptors = descriptors
94+
self.has_explicit_bind = False
9395
if extra_conf is not None:
9496
append_config(datadir, extra_conf)
97+
# Remember if there is bind=... in the config file.
98+
self.has_explicit_bind = any(e.startswith("bind=") for e in extra_conf)
9599
# Most callers will just need to add extra args to the standard list below.
96100
# For those callers that need more flexibity, they can just set the args property directly.
97101
# Note that common args are set in the config file (see initialize_datadir)
@@ -227,6 +231,17 @@ def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs
227231
if extra_args is None:
228232
extra_args = self.extra_args
229233

234+
# If listening and no -bind is given, then bitcoind would bind P2P ports on
235+
# 0.0.0.0:P and 127.0.0.1:18445 (for incoming Tor connections), where P is
236+
# a unique port chosen by the test framework and configured as port=P in
237+
# bitcoin.conf. To avoid collisions on 127.0.0.1:18445, change it to
238+
# 127.0.0.1:tor_port().
239+
will_listen = all(e != "-nolisten" and e != "-listen=0" for e in extra_args)
240+
has_explicit_bind = self.has_explicit_bind or any(e.startswith("-bind=") for e in extra_args)
241+
if will_listen and not has_explicit_bind:
242+
extra_args.append(f"-bind=0.0.0.0:{p2p_port(self.index)}")
243+
extra_args.append(f"-bind=127.0.0.1:{tor_port(self.index)}=onion")
244+
230245
self.use_v2transport = "-v2transport=1" in extra_args or (self.default_to_v2 and "-v2transport=0" not in extra_args)
231246

232247
# Add a new stdout and stderr file each time dashd is started

test/functional/test_framework/util.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,9 @@ def random_bytes(n):
320320

321321
# The maximum number of nodes a single test can spawn
322322
MAX_NODES = 20
323-
# Don't assign rpc or p2p ports lower than this
323+
# Don't assign p2p, rpc or tor ports lower than this
324324
PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000))
325-
# The number of ports to "reserve" for p2p and rpc, each
325+
# The number of ports to "reserve" for p2p, rpc and tor, each
326326
PORT_RANGE = 10000
327327

328328

@@ -362,7 +362,11 @@ def p2p_port(n):
362362

363363

364364
def rpc_port(n):
365-
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
365+
return p2p_port(n) + PORT_RANGE
366+
367+
368+
def tor_port(n):
369+
return p2p_port(n) + PORT_RANGE * 2
366370

367371

368372
def rpc_url(datadir, i, chain, rpchost=None):

0 commit comments

Comments
 (0)