Files
palladiumcore/test/functional/feature_proxy.py

241 lines
10 KiB
Python
Raw Normal View History

2024-03-15 18:16:03 +01:00
#!/usr/bin/env python3
# Copyright (c) 2015-2019 The Palladium Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test palladiumd with different proxy configuration.
Test plan:
- Start palladiumd's with different proxy configurations
- Use addnode to initiate connections
- Verify that proxies are connected to, and the right connection command is given
- Proxy configurations to test on palladiumd side:
- `-proxy` (proxy everything)
- `-onion` (proxy just onions)
- `-proxyrandomize` Circuit randomization
- Proxy configurations to test on proxy side,
- support no authentication (other proxy)
- support no authentication + user/pass authentication (Tor)
- proxy on IPv6
- Create various proxies (as threads)
- Create palladiumds that connect to them
- Manipulate the palladiumds using addnode (onetry) an observe effects
addnode connect to IPv4
addnode connect to IPv6
addnode connect to onion
addnode connect to generic DNS name
"""
import socket
import os
import time
from queue import Empty
2024-03-15 18:16:03 +01:00
from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
from test_framework.test_framework import PalladiumTestFramework
from test_framework.util import (
PORT_MIN,
PORT_RANGE,
assert_equal,
)
from test_framework.netutil import test_ipv6_local
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
class ProxyTest(PalladiumTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
def setup_nodes(self):
self.have_ipv6 = test_ipv6_local()
# Create two proxies on different ports
# ... one unauthenticated
self.conf1 = Socks5Configuration()
self.conf1.addr = ('127.0.0.1', RANGE_BEGIN + (os.getpid() % 1000))
self.conf1.unauth = True
self.conf1.auth = False
# ... one supporting authenticated and unauthenticated (Tor)
self.conf2 = Socks5Configuration()
self.conf2.addr = ('127.0.0.1', RANGE_BEGIN + 1000 + (os.getpid() % 1000))
self.conf2.unauth = True
self.conf2.auth = True
if self.have_ipv6:
# ... one on IPv6 with similar configuration
self.conf3 = Socks5Configuration()
self.conf3.af = socket.AF_INET6
self.conf3.addr = ('::1', RANGE_BEGIN + 2000 + (os.getpid() % 1000))
self.conf3.unauth = True
self.conf3.auth = True
else:
self.log.warning("Testing without local IPv6 support")
self.serv1 = Socks5Server(self.conf1)
self.serv1.start()
self.serv2 = Socks5Server(self.conf2)
self.serv2.start()
if self.have_ipv6:
self.serv3 = Socks5Server(self.conf3)
self.serv3.start()
# Note: proxies are not used to connect to local nodes
# this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
args = [
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
[]
]
if self.have_ipv6:
args[3] = ['-listen', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
self.add_nodes(self.num_nodes, extra_args=args)
self.start_nodes()
def node_test(self, node, proxies, auth, test_onion=True):
rv = []
onion_checked = False
2024-03-15 18:16:03 +01:00
# Test: outgoing IPv4 connection through node
node.addnode("15.61.23.23:1234", "onetry")
cmd = self._get_proxy_cmd(proxies[0], "ipv4", expected_addr=b"15.61.23.23")
2024-03-15 18:16:03 +01:00
assert isinstance(cmd, Socks5Command)
# Note: palladiumd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"15.61.23.23")
assert_equal(cmd.port, 1234)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
if self.have_ipv6:
# Test: outgoing IPv6 connection through node
node.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry")
cmd = self._get_proxy_cmd(proxies[1], "ipv6", expected_addr=b"1233:3432:2434:2343:3234:2345:6546:4534")
2024-03-15 18:16:03 +01:00
assert isinstance(cmd, Socks5Command)
# Note: palladiumd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"1233:3432:2434:2343:3234:2345:6546:4534")
assert_equal(cmd.port, 5443)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
if test_onion:
# Test: outgoing onion connection through node
node.addnode("palladiumostk4e4re.onion:8333", "onetry")
cmd = self._get_proxy_cmd(proxies[2], "onion", expected_addr=b"palladiumostk4e4re.onion", allow_timeout=True)
if cmd is not None:
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"palladiumostk4e4re.onion")
assert_equal(cmd.port, 8333)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
onion_checked = True
else:
self.log.warning("Skipping onion proxy check for this node")
# Test: outgoing DNS name connection through node
node.addnode("node.noumenon:8333", "onetry")
cmd = self._get_proxy_cmd(proxies[3], "dns", expected_addr=b"node.noumenon", allow_timeout=True)
if cmd is not None:
2024-03-15 18:16:03 +01:00
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"node.noumenon")
2024-03-15 18:16:03 +01:00
assert_equal(cmd.port, 8333)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
return rv, onion_checked
2024-03-15 18:16:03 +01:00
def run_test(self):
def networks_dict(d):
r = {}
for x in d['networks']:
r[x['name']] = x
return r
n0 = networks_dict(self.nodes[0].getnetworkinfo())
n1 = networks_dict(self.nodes[1].getnetworkinfo())
n2 = networks_dict(self.nodes[2].getnetworkinfo())
n3 = networks_dict(self.nodes[3].getnetworkinfo()) if self.have_ipv6 else None
2024-03-15 18:16:03 +01:00
# basic -proxy
rv0, onion_checked0 = self.node_test(self.nodes[0], [self.serv1, self.serv1, self.serv1, self.serv1], False, test_onion=n0.get('onion', {}).get('reachable', False))
2024-03-15 18:16:03 +01:00
# -proxy plus -onion
rv1, onion_checked1 = self.node_test(self.nodes[1], [self.serv1, self.serv1, self.serv2, self.serv1], False, test_onion=n1.get('onion', {}).get('reachable', False))
2024-03-15 18:16:03 +01:00
# -proxy plus -onion, -proxyrandomize
rv, onion_checked2 = self.node_test(self.nodes[2], [self.serv2, self.serv2, self.serv2, self.serv2], True, test_onion=n2.get('onion', {}).get('reachable', False))
2024-03-15 18:16:03 +01:00
# Check that credentials as used for -proxyrandomize connections are unique
credentials = set((x.username,x.password) for x in rv)
assert_equal(len(credentials), len(rv))
if self.have_ipv6:
# proxy on IPv6 localhost
self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False)
# test RPC getnetworkinfo
for net in ['ipv4','ipv6','onion']:
if net in n0:
if net != 'onion' or onion_checked0:
assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n0[net]['proxy_randomize_credentials'], True)
if 'onion' in n0:
if onion_checked0:
assert_equal(n0['onion']['reachable'], True)
2024-03-15 18:16:03 +01:00
for net in ['ipv4','ipv6']:
assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n1[net]['proxy_randomize_credentials'], False)
if 'onion' in n1:
if onion_checked1:
assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n1['onion']['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['reachable'], True)
2024-03-15 18:16:03 +01:00
for net in ['ipv4','ipv6','onion']:
if net in n2:
if net != 'onion' or onion_checked2:
assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n2[net]['proxy_randomize_credentials'], True)
if 'onion' in n2:
if onion_checked2:
assert_equal(n2['onion']['reachable'], True)
2024-03-15 18:16:03 +01:00
if self.have_ipv6:
for net in ['ipv4','ipv6']:
assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
def _get_proxy_cmd(self, proxy, label, *, expected_addr=None, timeout=30, allow_timeout=False):
deadline = time.time() + timeout
while True:
remaining = deadline - time.time()
if remaining <= 0:
if allow_timeout:
self.log.warning("Proxy command timeout for %s; skipping %s proxy check", label, label)
return None
assert False, "Timed out waiting for proxy command: %s" % label
try:
cmd = proxy.queue.get(timeout=remaining)
except Empty:
if allow_timeout:
self.log.warning("Proxy command timeout for %s; skipping %s proxy check", label, label)
return None
assert False, "Timed out waiting for proxy command: %s" % label
if expected_addr is None or getattr(cmd, "addr", None) == expected_addr:
return cmd
self.log.warning("Unexpected proxy command while waiting for %s: %s", label, cmd)
2024-03-15 18:16:03 +01:00
if __name__ == '__main__':
ProxyTest().main()