Skip to content

Commit bca346a

Browse files
committed
net: require P2P binds to succeed
In the Tor case, this prevents us from telling the Tor daemon to send our incoming connections from the Tor network to an address where we do not listen (we tried to listen but failed probably because another application is already listening). In the other cases (IPv4/IPv6 binds) this also prevents unpleasant surprises caused by continuing operations even on bind failure. For example, another application may be listening on portX, bitcoind tries to bind on portX and portY, only succeeds with portY and continues operation leaving the user thinking that his bitcoind is listening on portX whereas another application is listening (the error message in the log could easily be missed). Avoid having the functional testing framework start multiple `bitcoind`s that try to listen on the same `127.0.0.1:18445` (Tor listen for regtest) if `bind_to_localhost_only` is set to `False`. Also fix a typo in `test-shell.md` related to `bind_to_localhost_only`. Fixes bitcoin/bitcoin#22727
1 parent af55253 commit bca346a

File tree

4 files changed

+43
-12
lines changed

4 files changed

+43
-12
lines changed

src/net.cpp

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

31993199
bool CConnman::InitBinds(const Options& options)
32003200
{
3201-
bool fBound = false;
32023201
for (const auto& addrBind : options.vBinds) {
3203-
fBound |= Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None);
3202+
if (!Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None)) {
3203+
return false;
3204+
}
32043205
}
32053206
for (const auto& addrBind : options.vWhiteBinds) {
3206-
fBound |= Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags);
3207+
if (!Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags)) {
3208+
return false;
3209+
}
32073210
}
32083211
for (const auto& addr_bind : options.onion_binds) {
3209-
fBound |= Bind(addr_bind, BF_REPORT_ERROR | BF_DONT_ADVERTISE, NetPermissionFlags::None);
3212+
if (!Bind(addr_bind, BF_REPORT_ERROR | BF_DONT_ADVERTISE, NetPermissionFlags::None)) {
3213+
return false;
3214+
}
32103215
}
32113216
if (options.bind_on_any) {
3217+
// Don't consider errors to bind on IPv6 "::" fatal because the host OS
3218+
// may not have IPv6 support and the user did not explicitly ask us to
3219+
// bind on that.
3220+
const CService ipv6_any{in6_addr(IN6ADDR_ANY_INIT), GetListenPort()}; // ::
3221+
Bind(ipv6_any, BF_NONE, NetPermissionFlags::None);
3222+
32123223
struct in_addr inaddr_any;
32133224
inaddr_any.s_addr = htonl(INADDR_ANY);
3214-
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
3215-
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::None);
3216-
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::None);
3225+
const CService ipv4_any{inaddr_any, GetListenPort()}; // 0.0.0.0
3226+
if (!Bind(ipv4_any, BF_REPORT_ERROR, NetPermissionFlags::None)) {
3227+
return false;
3228+
}
32173229
}
3218-
return fBound;
3230+
return true;
32193231
}
32203232

32213233
bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)

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
@@ -39,6 +39,7 @@
3939
rpc_url,
4040
wait_until_helper_internal,
4141
p2p_port,
42+
tor_port,
4243
)
4344

4445
BITCOIND_PROC_WAIT_TIMEOUT = 60
@@ -88,8 +89,11 @@ def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor,
8889
self.coverage_dir = coverage_dir
8990
self.cwd = cwd
9091
self.descriptors = descriptors
92+
self.has_explicit_bind = False
9193
if extra_conf is not None:
9294
append_config(self.datadir_path, extra_conf)
95+
# Remember if there is bind=... in the config file.
96+
self.has_explicit_bind = any(e.startswith("bind=") for e in extra_conf)
9397
# Most callers will just need to add extra args to the standard list below.
9498
# For those callers that need more flexibility, they can just set the args property directly.
9599
# Note that common args are set in the config file (see initialize_datadir)
@@ -210,6 +214,17 @@ def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, env=None
210214
if extra_args is None:
211215
extra_args = self.extra_args
212216

217+
# If listening and no -bind is given, then bitcoind would bind P2P ports on
218+
# 0.0.0.0:P and 127.0.0.1:18445 (for incoming Tor connections), where P is
219+
# a unique port chosen by the test framework and configured as port=P in
220+
# bitcoin.conf. To avoid collisions on 127.0.0.1:18445, change it to
221+
# 127.0.0.1:tor_port().
222+
will_listen = all(e != "-nolisten" and e != "-listen=0" for e in extra_args)
223+
has_explicit_bind = self.has_explicit_bind or any(e.startswith("-bind=") for e in extra_args)
224+
if will_listen and not has_explicit_bind:
225+
extra_args.append(f"-bind=0.0.0.0:{p2p_port(self.index)}")
226+
extra_args.append(f"-bind=127.0.0.1:{tor_port(self.index)}=onion")
227+
213228
self.use_v2transport = "-v2transport=1" in extra_args or (self.default_to_v2 and "-v2transport=0" not in extra_args)
214229

215230
# Add a new stdout and stderr file each time bitcoind is started

test/functional/test_framework/util.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,9 @@ def sha256sum_file(filename):
309309

310310
# The maximum number of nodes a single test can spawn
311311
MAX_NODES = 12
312-
# Don't assign rpc or p2p ports lower than this
312+
# Don't assign p2p, rpc or tor ports lower than this
313313
PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000))
314-
# The number of ports to "reserve" for p2p and rpc, each
314+
# The number of ports to "reserve" for p2p, rpc and tor, each
315315
PORT_RANGE = 5000
316316

317317

@@ -351,7 +351,11 @@ def p2p_port(n):
351351

352352

353353
def rpc_port(n):
354-
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
354+
return p2p_port(n) + PORT_RANGE
355+
356+
357+
def tor_port(n):
358+
return p2p_port(n) + PORT_RANGE * 2
355359

356360

357361
def rpc_url(datadir, i, chain, rpchost):

0 commit comments

Comments
 (0)