mirror of
https://github.com/darkk/redsocks.git
synced 2025-08-26 19:55:30 +00:00
201 lines
6.2 KiB
Python
201 lines
6.2 KiB
Python
from functools import partial
|
|
from multiprocessing.dummy import Pool as ThrPool
|
|
from subprocess import check_call, check_output
|
|
import os
|
|
import time
|
|
|
|
import pytest
|
|
|
|
# 10.0.1.0/24 (br-web) -- web, squid, inetd, gw
|
|
# 10.0.2.0/24 (br-tank) -- tank, openwrt
|
|
# 10.0.8.0/24 (br-txrx) -- gw, openwrt
|
|
|
|
BR = {
|
|
'10.0.1': 'web',
|
|
'10.0.8': 'txrx',
|
|
'10.0.2': 'tank',
|
|
}
|
|
|
|
GW = {
|
|
'web': '10.0.1.1',
|
|
'txrx': '10.0.8.1',
|
|
'tank': '10.0.2.123',
|
|
}
|
|
|
|
SLEEPING_BEAST = 'sleep 3600'
|
|
|
|
class VM(object):
|
|
def __init__(self, name, tag, ip4=None, cmd='', docker_opt=''):
|
|
self.docker = os.environ.get('DOCKER_CMD', '/usr/bin/docker')
|
|
self.pipework = os.environ.get('PIPEWORK_CMD', '/usr/bin/pipework')
|
|
self.netns = '/var/run/netns'
|
|
self.dns = '8.8.8.8'
|
|
|
|
self.name, self.tag = name, tag
|
|
self.cmd, self.docker_opt = cmd, docker_opt
|
|
if ip4:
|
|
self.ip4 = ip4
|
|
self.sha = self.output('sudo docker run --detach --dns {dns} --name {name} --hostname {name} {docker_opt} {tag} {cmd}')
|
|
self.pid = int(self.output('docker inspect -f {{{{.State.Pid}}}} {sha}'))
|
|
if not os.path.exists(self.netns):
|
|
self.call('sudo mkdir {netns}')
|
|
self.call('sudo ln -sf /proc/{pid}/ns/net {netns}/{name}')
|
|
self.net()
|
|
while cmd != SLEEPING_BEAST and 'LISTEN' not in self.do('netstat -ltn'):
|
|
time.sleep(0.1)
|
|
|
|
def net(self):
|
|
self.net_noext()
|
|
self.net_br()
|
|
|
|
def net_br(self):
|
|
self.net_br_gw()
|
|
|
|
def net_noext(self):
|
|
self.call('sudo ip netns exec {name} ip link set dev eth0 down')
|
|
self.call('sudo ip netns exec {name} ip route replace unreachable {dns}/32')
|
|
|
|
def net_br_gw(self):
|
|
self.call('sudo {pipework} br-{br} -i {intif} -l {vethif} {name} {ip4}/24@{gw4}')
|
|
|
|
def net_br_nogw(self):
|
|
self.call('sudo {pipework} br-{br} -i {intif} -l {vethif} {name} {ip4}/24')
|
|
|
|
@property
|
|
def br(self):
|
|
return BR[self.ip4.rsplit('.', 1)[0]]
|
|
@property
|
|
def gw4(self):
|
|
return GW[self.br]
|
|
@property
|
|
def intif(self):
|
|
return {'web': 'ethw', 'tank': 'etht', 'txrx': 'ethx'}[self.br]
|
|
@property
|
|
def vethif(self):
|
|
return ('v' + self.intif + self.name)[:15] # IFNAMSIZ 16
|
|
|
|
def close(self):
|
|
if hasattr(self, 'sha'):
|
|
self.call('sudo docker stop --time 1 {sha}')
|
|
if not getattr(self, 'preserve_root', False):
|
|
self.call('sudo docker rm {sha}')
|
|
del self.sha
|
|
def fmt(self, cmd):
|
|
ctx = self.__dict__.copy()
|
|
for i in xrange(len(dir(self))):
|
|
try:
|
|
ret = cmd.format(**ctx).split()
|
|
break
|
|
except KeyError, e:
|
|
key = e.args[0]
|
|
ctx[key] = getattr(self, key)
|
|
return ret
|
|
def output(self, cmd):
|
|
return check_output(self.fmt(cmd))
|
|
def call(self, cmd):
|
|
check_call(self.fmt(cmd))
|
|
def do(self, cmd):
|
|
return self.output('sudo docker exec {sha} ' + cmd)
|
|
|
|
class WebVM(VM):
|
|
def __init__(self):
|
|
VM.__init__(self, 'web', 'redsocks/web', '10.0.1.80')
|
|
|
|
class InetdVM(VM):
|
|
def __init__(self):
|
|
VM.__init__(self, 'inetd', 'redsocks/inetd', '10.0.1.13')
|
|
|
|
class SquidVM(VM):
|
|
def __init__(self, no):
|
|
VM.__init__(self, 'squid-%d' % no, 'redsocks/squid', '10.0.1.%d' % no,
|
|
docker_opt='--ulimit nofile=65535:65535',
|
|
cmd='/etc/squid3/squid-%d.conf' % no)
|
|
def net(self):
|
|
self.net_br_nogw()
|
|
self.call('sudo ip netns exec {name} ip route replace 10.0.0.0/16 via 10.0.1.1')
|
|
|
|
class DanteVM(VM):
|
|
def __init__(self, no):
|
|
VM.__init__(self, 'dante-%d' % no, 'redsocks/dante', '10.0.1.%d' % (180 + no),
|
|
cmd='/etc/danted-%d.conf' % (1080 + no))
|
|
def net(self):
|
|
self.net_br_nogw()
|
|
self.call('sudo ip netns exec {name} ip route replace 10.0.0.0/16 via 10.0.1.1')
|
|
|
|
|
|
class GwVM(VM):
|
|
def __init__(self):
|
|
VM.__init__(self, 'gw', 'ubuntu:14.04', cmd=SLEEPING_BEAST)
|
|
def net_br(self):
|
|
self.ip4 = '10.0.1.1'
|
|
self.net_br_nogw()
|
|
self.ip4 = '10.0.8.1'
|
|
self.net_br_nogw()
|
|
del self.ip4
|
|
self.call('sudo ip netns exec {name} ip route replace unreachable 10.0.2.0/24')
|
|
|
|
class TankVM(VM):
|
|
def __init__(self, no):
|
|
assert 1 <= no <= 100
|
|
VM.__init__(self, 'tank%d' % no, 'redsocks/tank', '10.0.2.%d' % no, cmd=SLEEPING_BEAST)
|
|
|
|
class RegwVM(VM):
|
|
def __init__(self):
|
|
if int(os.environ.get('VALGRIND_TEST', '0')):
|
|
self.preserve_root = True
|
|
kw = {'cmd': 'valgrind --leak-check=full --show-leak-kinds=all /usr/local/sbin/redsocks -c /usr/local/etc/redsocks.conf'}
|
|
else:
|
|
kw = {}
|
|
VM.__init__(self, 'regw', 'redsocks/regw', **kw)
|
|
def net_br(self):
|
|
self.ip4 = '10.0.2.123'
|
|
self.net_br_nogw()
|
|
self.ip4 = '10.0.8.123'
|
|
self.net_br_gw()
|
|
del self.ip4
|
|
for t in TANKS.values():
|
|
self.call('sudo ip netns exec {name} iptables -t nat -A PREROUTING --source 10.0.2.%d/32 --dest 10.0.1.0/24 -p tcp -j REDIRECT --to-port %d' % (t, 12340 + t - TANKS_BASE))
|
|
|
|
def pmap(l):
|
|
#return map(lambda x: x(), l)
|
|
p = ThrPool(len(l))
|
|
try:
|
|
return p.map(lambda x: x(), l, chunksize=1)
|
|
finally:
|
|
p.close()
|
|
p.join()
|
|
|
|
TANKS_BASE = 10
|
|
TANKS = {
|
|
'connect_none': TANKS_BASE + 0,
|
|
'connect_basic': TANKS_BASE + 1,
|
|
'connect_digest': TANKS_BASE + 2,
|
|
'socks5_none': TANKS_BASE + 3,
|
|
'socks5_auth': TANKS_BASE + 4,
|
|
}
|
|
|
|
class _Network(object):
|
|
def __init__(self):
|
|
check_output('sudo docker ps'.split())
|
|
vm = [
|
|
GwVM,
|
|
WebVM,
|
|
InetdVM,
|
|
RegwVM,
|
|
partial(SquidVM, 8),
|
|
partial(SquidVM, 9),
|
|
partial(DanteVM, 0),
|
|
partial(DanteVM, 1),
|
|
]
|
|
for t in TANKS.values():
|
|
vm.append(partial(TankVM, t))
|
|
self.vm = {_.name: _ for _ in pmap(vm)} # pmap saves ~5 seconds
|
|
def close(self):
|
|
pmap([_.close for _ in self.vm.values()]) # pmap saves ~7 seconds
|
|
|
|
@pytest.fixture(scope="session")
|
|
def net(request):
|
|
n = _Network()
|
|
request.addfinalizer(n.close)
|
|
return n
|