2 # Copyright (c) 2015-2016 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.
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)
20 - Create various proxies (as threads)
21 - Create bitcoinds that connect to them
22 - Manipulate the bitcoinds using addnode (onetry) an observe effects
24 addnode connect to IPv4
25 addnode connect to IPv6
26 addnode connect to onion
27 addnode connect to generic DNS name
33 from test_framework
.socks5
import Socks5Configuration
, Socks5Command
, Socks5Server
, AddressType
34 from test_framework
.test_framework
import BitcoinTestFramework
35 from test_framework
.util
import (
40 from test_framework
.netutil
import test_ipv6_local
42 RANGE_BEGIN
= PORT_MIN
+ 2 * PORT_RANGE
# Start after p2p and rpc ports
44 class ProxyTest(BitcoinTestFramework
):
45 def set_test_params(self
):
48 def setup_nodes(self
):
49 self
.have_ipv6
= test_ipv6_local()
50 # Create two proxies on different ports
51 # ... one unauthenticated
52 self
.conf1
= Socks5Configuration()
53 self
.conf1
.addr
= ('127.0.0.1', RANGE_BEGIN
+ (os
.getpid() % 1000))
54 self
.conf1
.unauth
= True
55 self
.conf1
.auth
= False
56 # ... one supporting authenticated and unauthenticated (Tor)
57 self
.conf2
= Socks5Configuration()
58 self
.conf2
.addr
= ('127.0.0.1', RANGE_BEGIN
+ 1000 + (os
.getpid() % 1000))
59 self
.conf2
.unauth
= True
60 self
.conf2
.auth
= True
62 # ... one on IPv6 with similar configuration
63 self
.conf3
= Socks5Configuration()
64 self
.conf3
.af
= socket
.AF_INET6
65 self
.conf3
.addr
= ('::1', RANGE_BEGIN
+ 2000 + (os
.getpid() % 1000))
66 self
.conf3
.unauth
= True
67 self
.conf3
.auth
= True
69 self
.log
.warning("Testing without local IPv6 support")
71 self
.serv1
= Socks5Server(self
.conf1
)
73 self
.serv2
= Socks5Server(self
.conf2
)
76 self
.serv3
= Socks5Server(self
.conf3
)
79 # Note: proxies are not used to connect to local nodes
80 # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
82 ['-listen', '-proxy=%s:%i' % (self
.conf1
.addr
),'-proxyrandomize=1'],
83 ['-listen', '-proxy=%s:%i' % (self
.conf1
.addr
),'-onion=%s:%i' % (self
.conf2
.addr
),'-proxyrandomize=0'],
84 ['-listen', '-proxy=%s:%i' % (self
.conf2
.addr
),'-proxyrandomize=1'],
88 args
[3] = ['-listen', '-proxy=[%s]:%i' % (self
.conf3
.addr
),'-proxyrandomize=0', '-noonion']
89 self
.add_nodes(self
.num_nodes
, extra_args
=args
)
92 def node_test(self
, node
, proxies
, auth
, test_onion
=True):
94 # Test: outgoing IPv4 connection through node
95 node
.addnode("15.61.23.23:1234", "onetry")
96 cmd
= proxies
[0].queue
.get()
97 assert(isinstance(cmd
, Socks5Command
))
98 # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
99 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
100 assert_equal(cmd
.addr
, b
"15.61.23.23")
101 assert_equal(cmd
.port
, 1234)
103 assert_equal(cmd
.username
, None)
104 assert_equal(cmd
.password
, None)
108 # Test: outgoing IPv6 connection through node
109 node
.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry")
110 cmd
= proxies
[1].queue
.get()
111 assert(isinstance(cmd
, Socks5Command
))
112 # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
113 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
114 assert_equal(cmd
.addr
, b
"1233:3432:2434:2343:3234:2345:6546:4534")
115 assert_equal(cmd
.port
, 5443)
117 assert_equal(cmd
.username
, None)
118 assert_equal(cmd
.password
, None)
122 # Test: outgoing onion connection through node
123 node
.addnode("bitcoinostk4e4re.onion:8333", "onetry")
124 cmd
= proxies
[2].queue
.get()
125 assert(isinstance(cmd
, Socks5Command
))
126 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
127 assert_equal(cmd
.addr
, b
"bitcoinostk4e4re.onion")
128 assert_equal(cmd
.port
, 8333)
130 assert_equal(cmd
.username
, None)
131 assert_equal(cmd
.password
, None)
134 # Test: outgoing DNS name connection through node
135 node
.addnode("node.noumenon:8333", "onetry")
136 cmd
= proxies
[3].queue
.get()
137 assert(isinstance(cmd
, Socks5Command
))
138 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
139 assert_equal(cmd
.addr
, b
"node.noumenon")
140 assert_equal(cmd
.port
, 8333)
142 assert_equal(cmd
.username
, None)
143 assert_equal(cmd
.password
, None)
150 self
.node_test(self
.nodes
[0], [self
.serv1
, self
.serv1
, self
.serv1
, self
.serv1
], False)
153 self
.node_test(self
.nodes
[1], [self
.serv1
, self
.serv1
, self
.serv2
, self
.serv1
], False)
155 # -proxy plus -onion, -proxyrandomize
156 rv
= self
.node_test(self
.nodes
[2], [self
.serv2
, self
.serv2
, self
.serv2
, self
.serv2
], True)
157 # Check that credentials as used for -proxyrandomize connections are unique
158 credentials
= set((x
.username
,x
.password
) for x
in rv
)
159 assert_equal(len(credentials
), len(rv
))
162 # proxy on IPv6 localhost
163 self
.node_test(self
.nodes
[3], [self
.serv3
, self
.serv3
, self
.serv3
, self
.serv3
], False, False)
165 def networks_dict(d
):
167 for x
in d
['networks']:
171 # test RPC getnetworkinfo
172 n0
= networks_dict(self
.nodes
[0].getnetworkinfo())
173 for net
in ['ipv4','ipv6','onion']:
174 assert_equal(n0
[net
]['proxy'], '%s:%i' % (self
.conf1
.addr
))
175 assert_equal(n0
[net
]['proxy_randomize_credentials'], True)
176 assert_equal(n0
['onion']['reachable'], True)
178 n1
= networks_dict(self
.nodes
[1].getnetworkinfo())
179 for net
in ['ipv4','ipv6']:
180 assert_equal(n1
[net
]['proxy'], '%s:%i' % (self
.conf1
.addr
))
181 assert_equal(n1
[net
]['proxy_randomize_credentials'], False)
182 assert_equal(n1
['onion']['proxy'], '%s:%i' % (self
.conf2
.addr
))
183 assert_equal(n1
['onion']['proxy_randomize_credentials'], False)
184 assert_equal(n1
['onion']['reachable'], True)
186 n2
= networks_dict(self
.nodes
[2].getnetworkinfo())
187 for net
in ['ipv4','ipv6','onion']:
188 assert_equal(n2
[net
]['proxy'], '%s:%i' % (self
.conf2
.addr
))
189 assert_equal(n2
[net
]['proxy_randomize_credentials'], True)
190 assert_equal(n2
['onion']['reachable'], True)
193 n3
= networks_dict(self
.nodes
[3].getnetworkinfo())
194 for net
in ['ipv4','ipv6']:
195 assert_equal(n3
[net
]['proxy'], '[%s]:%i' % (self
.conf3
.addr
))
196 assert_equal(n3
[net
]['proxy_randomize_credentials'], False)
197 assert_equal(n3
['onion']['reachable'], False)
199 if __name__
== '__main__':