1#!/usr/bin/env python3 2# Copyright (c) 2015-2019 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"""Test bitcoind with different proxy configuration. 6 7Test plan: 8- Start bitcoind's with different proxy configurations 9- Use addnode to initiate connections 10- Verify that proxies are connected to, and the right connection command is given 11- Proxy configurations to test on bitcoind side: 12 - `-proxy` (proxy everything) 13 - `-onion` (proxy just onions) 14 - `-proxyrandomize` Circuit randomization 15- Proxy configurations to test on proxy side, 16 - support no authentication (other proxy) 17 - support no authentication + user/pass authentication (Tor) 18 - proxy on IPv6 19 20- Create various proxies (as threads) 21- Create nodes that connect to them 22- Manipulate the peer connections using addnode (onetry) and observe effects 23- Test the getpeerinfo `network` field for the peer 24 25addnode connect to IPv4 26addnode connect to IPv6 27addnode connect to onion 28addnode connect to generic DNS name 29 30- Test getnetworkinfo for each node 31""" 32 33import socket 34import os 35 36from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType 37from test_framework.test_framework import BitcoinTestFramework 38from test_framework.util import ( 39 PORT_MIN, 40 PORT_RANGE, 41 assert_equal, 42) 43from test_framework.netutil import test_ipv6_local 44 45RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports 46 47# Networks returned by RPC getpeerinfo, defined in src/netbase.cpp::GetNetworkName() 48NET_UNROUTABLE = "unroutable" 49NET_IPV4 = "ipv4" 50NET_IPV6 = "ipv6" 51NET_ONION = "onion" 52 53# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo() 54NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION}) 55 56 57class ProxyTest(BitcoinTestFramework): 58 def set_test_params(self): 59 self.num_nodes = 4 60 self.setup_clean_chain = True 61 62 def setup_nodes(self): 63 self.have_ipv6 = test_ipv6_local() 64 # Create two proxies on different ports 65 # ... one unauthenticated 66 self.conf1 = Socks5Configuration() 67 self.conf1.addr = ('127.0.0.1', RANGE_BEGIN + (os.getpid() % 1000)) 68 self.conf1.unauth = True 69 self.conf1.auth = False 70 # ... one supporting authenticated and unauthenticated (Tor) 71 self.conf2 = Socks5Configuration() 72 self.conf2.addr = ('127.0.0.1', RANGE_BEGIN + 1000 + (os.getpid() % 1000)) 73 self.conf2.unauth = True 74 self.conf2.auth = True 75 if self.have_ipv6: 76 # ... one on IPv6 with similar configuration 77 self.conf3 = Socks5Configuration() 78 self.conf3.af = socket.AF_INET6 79 self.conf3.addr = ('::1', RANGE_BEGIN + 2000 + (os.getpid() % 1000)) 80 self.conf3.unauth = True 81 self.conf3.auth = True 82 else: 83 self.log.warning("Testing without local IPv6 support") 84 85 self.serv1 = Socks5Server(self.conf1) 86 self.serv1.start() 87 self.serv2 = Socks5Server(self.conf2) 88 self.serv2.start() 89 if self.have_ipv6: 90 self.serv3 = Socks5Server(self.conf3) 91 self.serv3.start() 92 93 # Note: proxies are not used to connect to local nodes. This is because the proxy to 94 # use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost. 95 args = [ 96 ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'], 97 ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'], 98 ['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'], 99 [] 100 ] 101 if self.have_ipv6: 102 args[3] = ['-listen', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion'] 103 self.add_nodes(self.num_nodes, extra_args=args) 104 self.start_nodes() 105 106 def network_test(self, node, addr, network): 107 for peer in node.getpeerinfo(): 108 if peer["addr"] == addr: 109 assert_equal(peer["network"], network) 110 111 def node_test(self, node, proxies, auth, test_onion=True): 112 rv = [] 113 addr = "15.61.23.23:1234" 114 self.log.debug("Test: outgoing IPv4 connection through node for address {}".format(addr)) 115 node.addnode(addr, "onetry") 116 cmd = proxies[0].queue.get() 117 assert isinstance(cmd, Socks5Command) 118 # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 119 assert_equal(cmd.atyp, AddressType.DOMAINNAME) 120 assert_equal(cmd.addr, b"15.61.23.23") 121 assert_equal(cmd.port, 1234) 122 if not auth: 123 assert_equal(cmd.username, None) 124 assert_equal(cmd.password, None) 125 rv.append(cmd) 126 self.network_test(node, addr, network=NET_IPV4) 127 128 if self.have_ipv6: 129 addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443" 130 self.log.debug("Test: outgoing IPv6 connection through node for address {}".format(addr)) 131 node.addnode(addr, "onetry") 132 cmd = proxies[1].queue.get() 133 assert isinstance(cmd, Socks5Command) 134 # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 135 assert_equal(cmd.atyp, AddressType.DOMAINNAME) 136 assert_equal(cmd.addr, b"1233:3432:2434:2343:3234:2345:6546:4534") 137 assert_equal(cmd.port, 5443) 138 if not auth: 139 assert_equal(cmd.username, None) 140 assert_equal(cmd.password, None) 141 rv.append(cmd) 142 self.network_test(node, addr, network=NET_IPV6) 143 144 if test_onion: 145 addr = "bitcoinostk4e4re.onion:8333" 146 self.log.debug("Test: outgoing onion connection through node for address {}".format(addr)) 147 node.addnode(addr, "onetry") 148 cmd = proxies[2].queue.get() 149 assert isinstance(cmd, Socks5Command) 150 assert_equal(cmd.atyp, AddressType.DOMAINNAME) 151 assert_equal(cmd.addr, b"bitcoinostk4e4re.onion") 152 assert_equal(cmd.port, 8333) 153 if not auth: 154 assert_equal(cmd.username, None) 155 assert_equal(cmd.password, None) 156 rv.append(cmd) 157 self.network_test(node, addr, network=NET_ONION) 158 159 addr = "node.noumenon:8333" 160 self.log.debug("Test: outgoing DNS name connection through node for address {}".format(addr)) 161 node.addnode(addr, "onetry") 162 cmd = proxies[3].queue.get() 163 assert isinstance(cmd, Socks5Command) 164 assert_equal(cmd.atyp, AddressType.DOMAINNAME) 165 assert_equal(cmd.addr, b"node.noumenon") 166 assert_equal(cmd.port, 8333) 167 if not auth: 168 assert_equal(cmd.username, None) 169 assert_equal(cmd.password, None) 170 rv.append(cmd) 171 self.network_test(node, addr, network=NET_UNROUTABLE) 172 173 return rv 174 175 def run_test(self): 176 # basic -proxy 177 self.node_test(self.nodes[0], [self.serv1, self.serv1, self.serv1, self.serv1], False) 178 179 # -proxy plus -onion 180 self.node_test(self.nodes[1], [self.serv1, self.serv1, self.serv2, self.serv1], False) 181 182 # -proxy plus -onion, -proxyrandomize 183 rv = self.node_test(self.nodes[2], [self.serv2, self.serv2, self.serv2, self.serv2], True) 184 # Check that credentials as used for -proxyrandomize connections are unique 185 credentials = set((x.username,x.password) for x in rv) 186 assert_equal(len(credentials), len(rv)) 187 188 if self.have_ipv6: 189 # proxy on IPv6 localhost 190 self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False) 191 192 def networks_dict(d): 193 r = {} 194 for x in d['networks']: 195 r[x['name']] = x 196 return r 197 198 self.log.info("Test RPC getnetworkinfo") 199 n0 = networks_dict(self.nodes[0].getnetworkinfo()) 200 assert_equal(NETWORKS, n0.keys()) 201 for net in NETWORKS: 202 assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr)) 203 assert_equal(n0[net]['proxy_randomize_credentials'], True) 204 assert_equal(n0['onion']['reachable'], True) 205 206 n1 = networks_dict(self.nodes[1].getnetworkinfo()) 207 assert_equal(NETWORKS, n1.keys()) 208 for net in ['ipv4', 'ipv6']: 209 assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr)) 210 assert_equal(n1[net]['proxy_randomize_credentials'], False) 211 assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr)) 212 assert_equal(n1['onion']['proxy_randomize_credentials'], False) 213 assert_equal(n1['onion']['reachable'], True) 214 215 n2 = networks_dict(self.nodes[2].getnetworkinfo()) 216 assert_equal(NETWORKS, n2.keys()) 217 for net in NETWORKS: 218 assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr)) 219 assert_equal(n2[net]['proxy_randomize_credentials'], True) 220 assert_equal(n2['onion']['reachable'], True) 221 222 if self.have_ipv6: 223 n3 = networks_dict(self.nodes[3].getnetworkinfo()) 224 assert_equal(NETWORKS, n3.keys()) 225 for net in NETWORKS: 226 assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr)) 227 assert_equal(n3[net]['proxy_randomize_credentials'], False) 228 assert_equal(n3['onion']['reachable'], False) 229 230 231if __name__ == '__main__': 232 ProxyTest().main() 233