/ test / functional / feature_bind_extra.py
feature_bind_extra.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2014-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 starting bitcoind with -bind and/or -bind=...=onion, confirm that
  7  it binds to the expected ports, and verify that duplicate or conflicting
  8  -bind/-whitebind configurations are rejected with a descriptive error.
  9  """
 10  
 11  from itertools import combinations_with_replacement
 12  from test_framework.netutil import (
 13      addr_to_hex,
 14      get_bind_addrs,
 15  )
 16  from test_framework.test_framework import (
 17      BitcoinTestFramework,
 18  )
 19  from test_framework.test_node import ErrorMatch
 20  from test_framework.util import (
 21      assert_equal,
 22      p2p_port,
 23      rpc_port,
 24  )
 25  
 26  class BindExtraTest(BitcoinTestFramework):
 27      def set_test_params(self):
 28          self.setup_clean_chain = True
 29          # Avoid any -bind= on the command line. Force the framework to avoid
 30          # adding -bind=127.0.0.1.
 31          self.bind_to_localhost_only = False
 32          self.num_nodes = 3
 33  
 34      def skip_test_if_missing_module(self):
 35          # Due to OS-specific network stats queries, we only run on Linux.
 36          self.skip_if_platform_not_linux()
 37  
 38      def setup_network(self):
 39          loopback_ipv4 = addr_to_hex("127.0.0.1")
 40  
 41          # Start custom ports by reusing unused p2p ports
 42          def extra_port():
 43              port = p2p_port(extra_port.index)
 44              extra_port.index += 1
 45              return port
 46          extra_port.index = self.num_nodes
 47  
 48          # Array of tuples [command line arguments, expected bind addresses].
 49          self.expected = []
 50  
 51          # Node0, no normal -bind=... with -bind=...=onion, thus only the tor target.
 52          port = extra_port()
 53          self.expected.append(
 54              [
 55                  [f"-bind=127.0.0.1:{port}=onion"],
 56                  [(loopback_ipv4, port)],
 57              ],
 58          )
 59  
 60          # Node1, both -bind=... and -bind=...=onion.
 61          port = [extra_port(), extra_port()]
 62          self.expected.append(
 63              [
 64                  [f"-bind=127.0.0.1:{port[0]}", f"-bind=127.0.0.1:{port[1]}=onion"],
 65                  [(loopback_ipv4, port[0]), (loopback_ipv4, port[1])],
 66              ],
 67          )
 68  
 69          # Node2, no -bind=...=onion, thus no extra port for Tor target.
 70          port = extra_port()
 71          self.expected.append(
 72              [
 73                  [f"-bind=127.0.0.1:{port}"],
 74                  [(loopback_ipv4, port)],
 75              ],
 76          )
 77  
 78          self.extra_args = list(map(lambda e: e[0], self.expected))
 79          self.setup_nodes()
 80  
 81      def run_test(self):
 82          for i, (args, expected_services) in enumerate(self.expected):
 83              self.log.info(f"Checking listening ports of node {i} with {args}")
 84              pid = self.nodes[i].process.pid
 85              binds = set(get_bind_addrs(pid))
 86              # Remove IPv6 addresses because on some CI environments "::1" is not configured
 87              # on the system (so our test_ipv6_local() would return False), but it is
 88              # possible to bind on "::". This makes it unpredictable whether to expect
 89              # that bitcoind has bound on "::1" (for RPC) and "::" (for P2P).
 90              ipv6_addr_len_bytes = 32
 91              binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds))
 92              # Remove RPC ports. They are not relevant for this test.
 93              binds = set(filter(lambda e: e[1] != rpc_port(i), binds))
 94              assert_equal(binds, set(expected_services))
 95  
 96          self.stop_node(0)
 97  
 98          addr = "127.0.0.1:11012"
 99          for opt1, opt2 in combinations_with_replacement([f"-bind={addr}", f"-bind={addr}=onion", f"-whitebind=noban@{addr}"], 2):
100              self.nodes[0].assert_start_raises_init_error(
101                          [opt1, opt2],
102                          "Error: Duplicate binding configuration",
103                          match=ErrorMatch.PARTIAL_REGEX)
104  
105  if __name__ == '__main__':
106      BindExtraTest(__file__).main()