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
45 class ProxyTest(BitcoinTestFramework
):
49 self
.setup_clean_chain
= False
51 def setup_nodes(self
):
52 self
.have_ipv6
= test_ipv6_local()
53 # Create two proxies on different ports
54 # ... one unauthenticated
55 self
.conf1
= Socks5Configuration()
56 self
.conf1
.addr
= ('127.0.0.1', RANGE_BEGIN
+ (os
.getpid() % 1000))
57 self
.conf1
.unauth
= True
58 self
.conf1
.auth
= False
59 # ... one supporting authenticated and unauthenticated (Tor)
60 self
.conf2
= Socks5Configuration()
61 self
.conf2
.addr
= ('127.0.0.1', RANGE_BEGIN
+ 1000 + (os
.getpid() % 1000))
62 self
.conf2
.unauth
= True
63 self
.conf2
.auth
= True
65 # ... one on IPv6 with similar configuration
66 self
.conf3
= Socks5Configuration()
67 self
.conf3
.af
= socket
.AF_INET6
68 self
.conf3
.addr
= ('::1', RANGE_BEGIN
+ 2000 + (os
.getpid() % 1000))
69 self
.conf3
.unauth
= True
70 self
.conf3
.auth
= True
72 self
.log
.warning("Testing without local IPv6 support")
74 self
.serv1
= Socks5Server(self
.conf1
)
76 self
.serv2
= Socks5Server(self
.conf2
)
79 self
.serv3
= Socks5Server(self
.conf3
)
82 # Note: proxies are not used to connect to local nodes
83 # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
85 ['-listen', '-proxy=%s:%i' % (self
.conf1
.addr
),'-proxyrandomize=1'],
86 ['-listen', '-proxy=%s:%i' % (self
.conf1
.addr
),'-onion=%s:%i' % (self
.conf2
.addr
),'-proxyrandomize=0'],
87 ['-listen', '-proxy=%s:%i' % (self
.conf2
.addr
),'-proxyrandomize=1'],
91 args
[3] = ['-listen', '-proxy=[%s]:%i' % (self
.conf3
.addr
),'-proxyrandomize=0', '-noonion']
92 self
.add_nodes(self
.num_nodes
, extra_args
=args
)
95 def node_test(self
, node
, proxies
, auth
, test_onion
=True):
97 # Test: outgoing IPv4 connection through node
98 node
.addnode("15.61.23.23:1234", "onetry")
99 cmd
= proxies
[0].queue
.get()
100 assert(isinstance(cmd
, Socks5Command
))
101 # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
102 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
103 assert_equal(cmd
.addr
, b
"15.61.23.23")
104 assert_equal(cmd
.port
, 1234)
106 assert_equal(cmd
.username
, None)
107 assert_equal(cmd
.password
, None)
111 # Test: outgoing IPv6 connection through node
112 node
.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry")
113 cmd
= proxies
[1].queue
.get()
114 assert(isinstance(cmd
, Socks5Command
))
115 # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
116 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
117 assert_equal(cmd
.addr
, b
"1233:3432:2434:2343:3234:2345:6546:4534")
118 assert_equal(cmd
.port
, 5443)
120 assert_equal(cmd
.username
, None)
121 assert_equal(cmd
.password
, None)
125 # Test: outgoing onion connection through node
126 node
.addnode("bitcoinostk4e4re.onion:8333", "onetry")
127 cmd
= proxies
[2].queue
.get()
128 assert(isinstance(cmd
, Socks5Command
))
129 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
130 assert_equal(cmd
.addr
, b
"bitcoinostk4e4re.onion")
131 assert_equal(cmd
.port
, 8333)
133 assert_equal(cmd
.username
, None)
134 assert_equal(cmd
.password
, None)
137 # Test: outgoing DNS name connection through node
138 node
.addnode("node.noumenon:8333", "onetry")
139 cmd
= proxies
[3].queue
.get()
140 assert(isinstance(cmd
, Socks5Command
))
141 assert_equal(cmd
.atyp
, AddressType
.DOMAINNAME
)
142 assert_equal(cmd
.addr
, b
"node.noumenon")
143 assert_equal(cmd
.port
, 8333)
145 assert_equal(cmd
.username
, None)
146 assert_equal(cmd
.password
, None)
153 self
.node_test(self
.nodes
[0], [self
.serv1
, self
.serv1
, self
.serv1
, self
.serv1
], False)
156 self
.node_test(self
.nodes
[1], [self
.serv1
, self
.serv1
, self
.serv2
, self
.serv1
], False)
158 # -proxy plus -onion, -proxyrandomize
159 rv
= self
.node_test(self
.nodes
[2], [self
.serv2
, self
.serv2
, self
.serv2
, self
.serv2
], True)
160 # Check that credentials as used for -proxyrandomize connections are unique
161 credentials
= set((x
.username
,x
.password
) for x
in rv
)
162 assert_equal(len(credentials
), len(rv
))
165 # proxy on IPv6 localhost
166 self
.node_test(self
.nodes
[3], [self
.serv3
, self
.serv3
, self
.serv3
, self
.serv3
], False, False)
168 def networks_dict(d
):
170 for x
in d
['networks']:
174 # test RPC getnetworkinfo
175 n0
= networks_dict(self
.nodes
[0].getnetworkinfo())
176 for net
in ['ipv4','ipv6','onion']:
177 assert_equal(n0
[net
]['proxy'], '%s:%i' % (self
.conf1
.addr
))
178 assert_equal(n0
[net
]['proxy_randomize_credentials'], True)
179 assert_equal(n0
['onion']['reachable'], True)
181 n1
= networks_dict(self
.nodes
[1].getnetworkinfo())
182 for net
in ['ipv4','ipv6']:
183 assert_equal(n1
[net
]['proxy'], '%s:%i' % (self
.conf1
.addr
))
184 assert_equal(n1
[net
]['proxy_randomize_credentials'], False)
185 assert_equal(n1
['onion']['proxy'], '%s:%i' % (self
.conf2
.addr
))
186 assert_equal(n1
['onion']['proxy_randomize_credentials'], False)
187 assert_equal(n1
['onion']['reachable'], True)
189 n2
= networks_dict(self
.nodes
[2].getnetworkinfo())
190 for net
in ['ipv4','ipv6','onion']:
191 assert_equal(n2
[net
]['proxy'], '%s:%i' % (self
.conf2
.addr
))
192 assert_equal(n2
[net
]['proxy_randomize_credentials'], True)
193 assert_equal(n2
['onion']['reachable'], True)
196 n3
= networks_dict(self
.nodes
[3].getnetworkinfo())
197 for net
in ['ipv4','ipv6']:
198 assert_equal(n3
[net
]['proxy'], '[%s]:%i' % (self
.conf3
.addr
))
199 assert_equal(n3
[net
]['proxy_randomize_credentials'], False)
200 assert_equal(n3
['onion']['reachable'], False)
202 if __name__
== '__main__':