#!/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()