Files
bitcoin/test/functional/feature_bind_port_discover.py
Vasil Dimov 1b93983bf5 test: make feature_bind_port_(discover|externalip).py auto-detect the skip condition
Instead of requiring a run with an explicit `--ihave1111and2222`, detect
whether the routable addresses are set up and if not, then skip the test.

To detect whether the addresses are set use `bitcoind` - start it
and ask it to bind on them and see if it will error with
"Unable to bind". Since this is what the tests do anyway, just start
the nodes and see if an exception will be raised like
`FailedToStartError` / "Unable to bind".

This makes it possible for the CI to run
`feature_bind_port_discover.py` and
`feature_bind_port_externalip.py` by just setting up the
addresses, without having to explicitly provide `--ihave1111and2222`.

Co-authored-by: willcl-ark <will@256k1.dev>
2026-04-29 14:18:41 +02:00

119 lines
5.2 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2020-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test that -discover does not add all interfaces' addresses if we listen on only some of them
"""
from test_framework.test_framework import BitcoinTestFramework, SkipTest
from test_framework.test_node import (
FailedToStartError,
)
from test_framework.util import (
assert_equal,
assert_not_equal,
p2p_port,
tor_port,
)
# We need to bind to routable addresses for this test. Both addresses must be on an
# interface that is UP and not a loopback interface (IFF_LOOPBACK). To set these
# routable addresses on the machine, use:
# Linux:
# First find your interfaces: ip addr show
# Then use your actual interface names (replace INTERFACE_NAME with yours):
# ip addr add 1.1.1.5/32 dev INTERFACE_NAME && ip addr add 1111:1111::5/128 dev INTERFACE_NAME # to set up
# ip addr del 1.1.1.5/32 dev INTERFACE_NAME && ip addr del 1111:1111::5/128 dev INTERFACE_NAME # to remove it
#
# FreeBSD and MacOS:
# ifconfig INTERFACE_NAME 1.1.1.5/32 alias && ifconfig INTERFACE_NAME inet6 1111:1111::5/128 alias # to set up
# ifconfig INTERFACE_NAME 1.1.1.5 -alias && ifconfig INTERFACE_NAME inet6 1111:1111::5 -alias # to remove it, after the test
ADDR1 = '1.1.1.5' # This and the address below are set in the CI environment, don't change it just here (keep them in sync).
ADDR2 = '1111:1111::5'
class BindPortDiscoverTest(BitcoinTestFramework):
def set_test_params(self):
# Avoid any -bind= on the command line. Force the framework to avoid adding -bind=127.0.0.1.
self.bind_to_localhost_only = False
# Get dynamic ports for each node from the test framework
self.bind_ports = [
p2p_port(0),
p2p_port(2), # node0 will use their port + 1 for onion listen, which is the same as p2p_port(1), so avoid collision
p2p_port(3),
p2p_port(4),
]
self.extra_args = [
['-discover', f'-port={self.bind_ports[0]}', '-listen=1'], # Without any -bind
['-discover', f'-bind=0.0.0.0:{self.bind_ports[1]}'], # Explicit -bind=0.0.0.0
# Explicit -whitebind=0.0.0.0, add onion bind to avoid port conflict
['-discover', f'-whitebind=0.0.0.0:{self.bind_ports[2]}', f'-bind=127.0.0.1:{tor_port(3)}=onion'],
['-discover', f'-bind={ADDR1}:{self.bind_ports[3]}'], # Explicit -bind=routable_addr
]
self.num_nodes = len(self.extra_args)
def setup_network(self):
"""
Override to avoid connecting nodes together. This test intentionally does not connect nodes
because each node is bound to a different address or interface, and connections are not needed.
"""
self.setup_nodes()
def setup_nodes(self):
"""
Override to set has_explicit_bind=True for nodes with explicit bind arguments.
"""
self.add_nodes(self.num_nodes, self.extra_args)
# TestNode.start() will add -bind= to extra_args if has_explicit_bind is
# False. We do not want any -bind= thus set has_explicit_bind to True.
for node in self.nodes:
node.has_explicit_bind = True
try:
self.start_nodes()
except FailedToStartError as e:
self.cleanup_partially_started_nodes()
if 'Unable to bind to ' in str(e):
raise SkipTest(
f'To run this test make sure that {ADDR1} and {ADDR2} '
'(routable addresses) are assigned to non-loopback '
'interfaces on this machine')
raise
def run_test(self):
self.log.info(
"Test that if -bind= is not passed or -bind=0.0.0.0 is used then all addresses are "
"added to localaddresses")
for i in [0, 1, 2]:
found_addr1 = False
found_addr2 = False
localaddresses = self.nodes[i].getnetworkinfo()['localaddresses']
for local in localaddresses:
if local['address'] == ADDR1:
found_addr1 = True
assert_equal(local['port'], self.bind_ports[i])
if local['address'] == ADDR2:
found_addr2 = True
assert_equal(local['port'], self.bind_ports[i])
if not found_addr1:
self.log.error(f"Address {ADDR1} not found in node{i}'s local addresses: {localaddresses}")
assert False
if not found_addr2:
self.log.error(f"Address {ADDR2} not found in node{i}'s local addresses: {localaddresses}")
assert False
self.log.info(
"Test that if -bind=routable_addr is passed then only that address is "
"added to localaddresses")
found_addr1 = False
i = 3
for local in self.nodes[i].getnetworkinfo()['localaddresses']:
if local['address'] == ADDR1:
found_addr1 = True
assert_equal(local['port'], self.bind_ports[i])
assert_not_equal(local['address'], ADDR2)
assert found_addr1
if __name__ == '__main__':
BindPortDiscoverTest(__file__).main()