From 2e9ce11ea15c303b769c2ab5e327c7372cfef2c7 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 18 Jan 2015 18:32:04 +0800 Subject: [PATCH 001/104] bump --- CHANGES | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 27c8562..e90ad72 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.6.5 2015-01-18 +- Try both 32 bit and 64 bit dll on Windows + 2.6.4 2015-01-14 - Also search lib* when searching libraries diff --git a/setup.py b/setup.py index c287c5c..8c022ea 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.4", + version="2.6.5", license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 5e5d25efd989903648f7e6d1d5865eb6f890b1c8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 19 Jan 2015 16:20:08 +0800 Subject: [PATCH 002/104] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04aaa02..fbdb9c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,8 @@ a pull request, or ask some of your friends to do so. 3. We don't answer questions of any other types here. Since very few people are watching the issue tracker here, you'll probably get no help from here. Read [Troubleshooting] and get help from forums or [mailing lists]. +4. Issues in languages other than English will be Google translated into English +later. [Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting From f4052fbc84904bd377965adbb5ff7e58b4754e8e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 21 Jan 2015 14:32:21 +0800 Subject: [PATCH 003/104] fix MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 1dc4c8e..1882dd7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -recursive-include *.py +recursive-include shadowsocks *.py include README.rst include LICENSE From 0e6a4cd8ff118b9362be8a33c961290081a8373f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 23 Jan 2015 14:06:01 +0800 Subject: [PATCH 004/104] output python version in unit tests --- .jenkins.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.jenkins.sh b/.jenkins.sh index bb58e04..28583f9 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -24,6 +24,7 @@ function run_test { return 0 } +python --version coverage erase mkdir tmp run_test pep8 . From 1f8819f790e20ff30a8d6357d9a2509bc3229d17 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 23 Jan 2015 17:33:48 +0800 Subject: [PATCH 005/104] fix #270 --- .jenkins.sh | 2 +- shadowsocks/tcprelay.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.jenkins.sh b/.jenkins.sh index 28583f9..f14969f 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -46,7 +46,7 @@ run_test python tests/test.py --with-coverage -c tests/workers.json run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv" run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" -run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +run_test python tests/test.py --with-coverage --should-fail --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index c148208..3b48901 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -387,7 +387,7 @@ class TCPRelayHandler(object): self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) return - except (OSError, IOError) as e: + except Exception as e: logging.error(e) if self._config['verbose']: traceback.print_exc() From 70ebd2ef285cc52f903dce2ea990936a8581f731 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 23 Jan 2015 17:35:32 +0800 Subject: [PATCH 006/104] bump --- CHANGES | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e90ad72..6e0331f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.6.6 2015-01-23 +- Fix a crash in forbidden list + 2.6.5 2015-01-18 - Try both 32 bit and 64 bit dll on Windows diff --git a/setup.py b/setup.py index 8c022ea..33cc44c 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.5", + version="2.6.6", license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 51f47ccb9167721527aac69a909d9ea83c93929c Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 24 Jan 2015 14:28:39 +0800 Subject: [PATCH 007/104] update coverage url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e45c91..efc4406 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Bugs and Issues [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat [Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File [Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks -[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/htmlcov/index.html +[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux01/htmlcov/index.html [Debian sid]: https://packages.debian.org/unstable/python/shadowsocks [iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help [Issue Tracker]: https://github.com/shadowsocks/shadowsocks/issues?state=open From 4a2d98b28005ff2e53ab874d460eb5e463876d89 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 24 Jan 2015 14:43:11 +0800 Subject: [PATCH 008/104] update coverage url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efc4406..aba545b 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Bugs and Issues [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat [Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File [Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks -[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux01/htmlcov/index.html +[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html [Debian sid]: https://packages.debian.org/unstable/python/shadowsocks [iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help [Issue Tracker]: https://github.com/shadowsocks/shadowsocks/issues?state=open From 5316b3bf115ce915bfa182afdf926045625304fc Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 28 Jan 2015 17:17:05 +0800 Subject: [PATCH 009/104] update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fb96264..6c1b61e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ pip-log.txt # Unit test / coverage reports htmlcov -.coverage +.coverage* .tox #Translations From ada97ab6d9ae3b9ad1e6d91ab92cc6f8c37259c2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 31 Jan 2015 02:58:40 +0800 Subject: [PATCH 010/104] improve comments --- shadowsocks/tcprelay.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 3b48901..d68e424 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -43,30 +43,22 @@ TIMEOUT_PRECISION = 4 MSG_FASTOPEN = 0x20000000 -# SOCKS CMD defination +# SOCKS command definition CMD_CONNECT = 1 CMD_BIND = 2 CMD_UDP_ASSOCIATE = 3 -# TCP Relay can be either sslocal or ssserver -# for sslocal it is called is_local=True - # for each opening port, we have a TCP Relay + # for each connection, we have a TCP Relay Handler to handle the connection # for each handler, we have 2 sockets: # local: connected to the client # remote: connected to remote server -# for each handler, we have 2 streams: -# upstream: from client to server direction -# read local and write to remote -# downstream: from server to client direction -# read remote and write to local - # for each handler, it could be at one of several stages: -# sslocal: +# as sslocal: # stage 0 SOCKS hello received from local, send hello to local # stage 1 addr received from local, query DNS for remote # stage 2 UDP assoc @@ -74,7 +66,7 @@ CMD_UDP_ASSOCIATE = 3 # stage 4 still connecting, more data from local received # stage 5 remote connected, piping local and remote -# ssserver: +# as ssserver: # stage 0 just jump to stage 1 # stage 1 addr received from local, query DNS for remote # stage 3 DNS resolved, connect to remote @@ -89,11 +81,16 @@ STAGE_CONNECTING = 4 STAGE_STREAM = 5 STAGE_DESTROYED = -1 -# stream direction +# for each handler, we have 2 stream directions: +# upstream: from client to server direction +# read local and write to remote +# downstream: from server to client direction +# read remote and write to local + STREAM_UP = 0 STREAM_DOWN = 1 -# stream wait status, indicating it's waiting for reading, etc +# for each stream, it's waiting for reading, or writing, or both WAIT_STATUS_INIT = 0 WAIT_STATUS_READING = 1 WAIT_STATUS_WRITING = 2 @@ -112,6 +109,9 @@ class TCPRelayHandler(object): self._remote_sock = None self._config = config self._dns_resolver = dns_resolver + + # TCP Relay works as either sslocal or ssserver + # if is_local, this is sslocal self._is_local = is_local self._stage = STAGE_INIT self._encryptor = encrypt.Encryptor(config['password'], From 8783e0e9aea644f3839bfbcf9763090a1c39d0f1 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sat, 31 Jan 2015 11:56:17 +0800 Subject: [PATCH 011/104] Move is_ip from asyncdns to common Since implement CIDR forbidden need this function, move it to common file seems to be a better choice. --- shadowsocks/asyncdns.py | 18 +++--------------- shadowsocks/common.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 6f60dc9..6fee6b9 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -233,18 +233,6 @@ def parse_response(data): return None -def is_ip(address): - for family in (socket.AF_INET, socket.AF_INET6): - try: - if type(address) != str: - address = address.decode('utf8') - socket.inet_pton(family, address) - return family - except (TypeError, ValueError, OSError, IOError): - pass - return False - - def is_valid_hostname(hostname): if len(hostname) > 255: return False @@ -296,7 +284,7 @@ class DNSResolver(object): parts = line.split() if len(parts) >= 2: server = parts[1] - if is_ip(server) == socket.AF_INET: + if common.is_ip(server) == socket.AF_INET: if type(server) != str: server = server.decode('utf8') self._servers.append(server) @@ -316,7 +304,7 @@ class DNSResolver(object): parts = line.split() if len(parts) >= 2: ip = parts[0] - if is_ip(ip): + if common.is_ip(ip): for i in range(1, len(parts)): hostname = parts[i] if hostname: @@ -423,7 +411,7 @@ class DNSResolver(object): hostname = hostname.encode('utf8') if not hostname: callback(None, Exception('empty hostname')) - elif is_ip(hostname): + elif common.is_ip(hostname): callback((hostname, hostname), None) elif hostname in self._hosts: logging.debug('hit hosts: %s', hostname) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index e4f698c..2be17e5 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -101,6 +101,18 @@ def inet_pton(family, addr): raise RuntimeError("What family?") +def is_ip(address): + for family in (socket.AF_INET, socket.AF_INET6): + try: + if type(address) != str: + address = address.decode('utf8') + inet_pton(family, address) + return family + except (TypeError, ValueError, OSError, IOError): + pass + return False + + def patch_socket(): if not hasattr(socket, 'inet_pton'): socket.inet_pton = inet_pton From 100ebcf064f4a8e5840012bdccef106794208569 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sat, 31 Jan 2015 19:50:10 +0800 Subject: [PATCH 012/104] Add IPNetwork class to support CIDR calculation Usage: Use IPNetwork(str|list) to create an IPNetwork object. Use operator 'in' to determine whether the specified IP address is in the IP network or not, like: >>> '192.168.1.1' in IPNetwork('192.168.1.0/24') True Both IPv4 and IPv6 address are supported. Note: When using string to initialize the IPNetwork, a comma seperated IP network list should be provided. Currently, IPNetwork just support standard CIDR like: x.x.x.x/y eg. 192.168.1.0/24 ::x/y eg. ::1/10 If pure IP address was provided, it will be treated as implicit IP network, like 192.168.0.0 will be treated as 192.168.0.0/16 and 192.168.1.1 will be treated as 192.168.1.1/32 This implicit translate may cause some unexpected behavior, like user provide 192.168.2.0 and expect it will be treated as 192.168.2.0/24 but actually it will be translated to 192.168.2.0/23 because there are 9 continuous 0 from right. In order to avoid confusion, a warning message will be displayed when pure IP address was provided. Other variants of CIDR are not supported yet. --- shadowsocks/common.py | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 2be17e5..d582923 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -184,6 +184,57 @@ def parse_header(data): return addrtype, to_bytes(dest_addr), dest_port, header_length +class IPNetwork(object): + ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128} + + def __init__(self, addrs): + self._network_list_v4 = [] + self._network_list_v6 = [] + if type(addrs) == str: + addrs = addrs.split(',') + map(self.add_network, addrs) + + def add_network(self, addr): + block = addr.split('/') + addr_family = is_ip(block[0]) + if addr_family is socket.AF_INET: + ip, = struct.unpack("!I", socket.inet_aton(block[0])) + elif addr_family is socket.AF_INET6: + hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0])) + ip = (hi << 64) | lo + else: + raise SyntaxError("Not a valid CIDR notation: %s" % addr) + if len(block) is 1: + prefix_size = 0 + while ((ip & 1) == 0): + ip >>= 1 + prefix_size += 1 + logging.warn("You did't specify CIDR routing prefix size for %s, " + "implicit treated as %s/%d" % (addr, addr, + IPNetwork.ADDRLENGTH[addr_family] - prefix_size)) + elif block[1].isdigit() and int(block[1]) <= IPNetwork.ADDRLENGTH[addr_family]: + prefix_size = IPNetwork.ADDRLENGTH[addr_family] - int(block[1]) + ip >>= prefix_size + else: + raise SyntaxError("Not a valid CIDR notation: %s" % addr) + if addr_family is socket.AF_INET: + self._network_list_v4.append((ip, prefix_size)) + else: + self._network_list_v6.append((ip, prefix_size)) + + def __contains__(self, addr): + addr_family = is_ip(addr) + if addr_family is socket.AF_INET: + ip, = struct.unpack("!I", socket.inet_aton(addr)) + return any(map(lambda (naddr, ps): naddr == ip >> ps, self._network_list_v4)) + elif addr_family is socket.AF_INET6: + hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr)) + ip = (hi << 64) | lo + return any(map(lambda (naddr, ps): naddr == ip >> ps, self._network_list_v6)) + else: + return False + + def test_inet_conv(): ipv4 = b'8.8.4.4' b = inet_pton(socket.AF_INET, ipv4) @@ -210,7 +261,23 @@ def test_pack_header(): assert pack_addr(b'www.google.com') == b'\x03\x0ewww.google.com' +def test_ip_network(): + ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.168.2.0') + assert '127.0.0.1' in ip_network + assert '127.0.1.1' not in ip_network + assert ':ff:ffff' in ip_network + assert '::ffff:1' not in ip_network + assert '::1' in ip_network + assert '::2' not in ip_network + assert '192.168.1.1' in ip_network + assert '192.168.1.2' not in ip_network + assert '192.168.2.1' in ip_network + assert '192.168.3.1' in ip_network # 192.168.2.0 is treated as 192.168.2.0/23 + assert 'www.google.com' not in ip_network + + if __name__ == '__main__': test_inet_conv() test_parse_header() test_pack_header() + test_ip_network() From 8af359ae05d6fa60b9f60e6f732c6e02bb4b6b93 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sat, 31 Jan 2015 20:32:09 +0800 Subject: [PATCH 013/104] Use IPNetwork supporting forbidden ip feature in config utils This commit also make "forbidden_ip" field available in config file. If no forbidden ip specified in command line and config file, default to "127.0.0.0/8,::1". --- shadowsocks/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index a51c965..0c77125 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -29,7 +29,7 @@ import json import sys import getopt import logging -from shadowsocks.common import to_bytes, to_str +from shadowsocks.common import to_bytes, to_str, IPNetwork VERBOSE_LEVEL = 5 @@ -186,6 +186,7 @@ def get_config(is_local): config['verbose'] = config.get('verbose', False) config['local_address'] = config.get('local_address', '127.0.0.1') config['local_port'] = config.get('local_port', 1080) + config['forbidden_ip'] = IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1')) if is_local: if config.get('server', None) is None: logging.error('server addr not specified') From aa28796524eb7ff9ccb9a73afa4353079d2a9f71 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 1 Feb 2015 00:17:03 +0800 Subject: [PATCH 014/104] Make common fit PEP8 --- shadowsocks/common.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index d582923..0f94e07 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -185,7 +185,7 @@ def parse_header(data): class IPNetwork(object): - ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128} + ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128, False: 0} def __init__(self, addrs): self._network_list_v4 = [] @@ -197,6 +197,7 @@ class IPNetwork(object): def add_network(self, addr): block = addr.split('/') addr_family = is_ip(block[0]) + addr_len = IPNetwork.ADDRLENGTH[addr_family] if addr_family is socket.AF_INET: ip, = struct.unpack("!I", socket.inet_aton(block[0])) elif addr_family is socket.AF_INET6: @@ -210,10 +211,9 @@ class IPNetwork(object): ip >>= 1 prefix_size += 1 logging.warn("You did't specify CIDR routing prefix size for %s, " - "implicit treated as %s/%d" % (addr, addr, - IPNetwork.ADDRLENGTH[addr_family] - prefix_size)) - elif block[1].isdigit() and int(block[1]) <= IPNetwork.ADDRLENGTH[addr_family]: - prefix_size = IPNetwork.ADDRLENGTH[addr_family] - int(block[1]) + "implicit treated as %s/%d" % (addr, addr, addr_len)) + elif block[1].isdigit() and int(block[1]) <= addr_len: + prefix_size = addr_len - int(block[1]) ip >>= prefix_size else: raise SyntaxError("Not a valid CIDR notation: %s" % addr) @@ -226,11 +226,13 @@ class IPNetwork(object): addr_family = is_ip(addr) if addr_family is socket.AF_INET: ip, = struct.unpack("!I", socket.inet_aton(addr)) - return any(map(lambda (naddr, ps): naddr == ip >> ps, self._network_list_v4)) + return any(map(lambda (n, ps): n == ip >> ps, + self._network_list_v4)) elif addr_family is socket.AF_INET6: hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr)) ip = (hi << 64) | lo - return any(map(lambda (naddr, ps): naddr == ip >> ps, self._network_list_v6)) + return any(map(lambda (n, ps): n == ip >> ps, + self._network_list_v6)) else: return False @@ -262,7 +264,7 @@ def test_pack_header(): def test_ip_network(): - ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.168.2.0') + ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.0.2.0') assert '127.0.0.1' in ip_network assert '127.0.1.1' not in ip_network assert ':ff:ffff' in ip_network @@ -271,8 +273,8 @@ def test_ip_network(): assert '::2' not in ip_network assert '192.168.1.1' in ip_network assert '192.168.1.2' not in ip_network - assert '192.168.2.1' in ip_network - assert '192.168.3.1' in ip_network # 192.168.2.0 is treated as 192.168.2.0/23 + assert '192.0.2.1' in ip_network + assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23 assert 'www.google.com' not in ip_network From b11d8489862765a2aa13d1594c855c4e652f8ccf Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 1 Feb 2015 00:42:12 +0800 Subject: [PATCH 015/104] Fix for Python3 lambda behavior change In Python3, lambda no longer support use tuple as syntax. So, ugly changes is inevitable. --- shadowsocks/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 0f94e07..68ca025 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -226,12 +226,12 @@ class IPNetwork(object): addr_family = is_ip(addr) if addr_family is socket.AF_INET: ip, = struct.unpack("!I", socket.inet_aton(addr)) - return any(map(lambda (n, ps): n == ip >> ps, + return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], self._network_list_v4)) elif addr_family is socket.AF_INET6: hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr)) ip = (hi << 64) | lo - return any(map(lambda (n, ps): n == ip >> ps, + return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], self._network_list_v6)) else: return False From 070108f78bb81f4dc6391fd575d5268f51481cea Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 1 Feb 2015 00:48:15 +0800 Subject: [PATCH 016/104] Disable forbidden ip feature for local shadowsocks Since forbidden ip is server-side only, disable it for local-side. This commit also supress warning about IPv6 loopback because I can confirm ::1/128 is the only loopback address, not like IPv4. --- shadowsocks/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 0c77125..6ea3daa 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -186,7 +186,6 @@ def get_config(is_local): config['verbose'] = config.get('verbose', False) config['local_address'] = config.get('local_address', '127.0.0.1') config['local_port'] = config.get('local_port', 1080) - config['forbidden_ip'] = IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1')) if is_local: if config.get('server', None) is None: logging.error('server addr not specified') @@ -194,6 +193,8 @@ def get_config(is_local): sys.exit(2) else: config['server'] = config.get('server', '0.0.0.0') + config['forbidden_ip'] = \ + IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) config['server_port'] = config.get('server_port', 8388) if is_local and not config.get('password', None): From 79b9b53dbea59627d43ec7ee4eb2dd54777460be Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 1 Feb 2015 01:57:17 +0800 Subject: [PATCH 017/104] Never process empty string and prevent infinite loop If user provide an empty string as network range, inet_pton will treate it as an IPv6 unspecified address, it seems a bug but I can't confirm. Then empty string will be converted to 0, 0 & 1 always be zero, so it caused dead loop. --- shadowsocks/common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 68ca025..d4a9a67 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -195,6 +195,8 @@ class IPNetwork(object): map(self.add_network, addrs) def add_network(self, addr): + if addr is "": + return block = addr.split('/') addr_family = is_ip(block[0]) addr_len = IPNetwork.ADDRLENGTH[addr_family] @@ -207,7 +209,7 @@ class IPNetwork(object): raise SyntaxError("Not a valid CIDR notation: %s" % addr) if len(block) is 1: prefix_size = 0 - while ((ip & 1) == 0): + while (ip & 1) == 0 and ip is not 0: ip >>= 1 prefix_size += 1 logging.warn("You did't specify CIDR routing prefix size for %s, " From a0aa9173a8001b5c20e270d7b5dcec28172cfa3f Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 1 Feb 2015 02:37:30 +0800 Subject: [PATCH 018/104] Fix for Python3 map changed behavior In Python3, map returns an iterator instead of list in Python2, which cause map "lazier" than before, wrap with list() force it running. --- shadowsocks/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index d4a9a67..0c4e278 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -192,7 +192,7 @@ class IPNetwork(object): self._network_list_v6 = [] if type(addrs) == str: addrs = addrs.split(',') - map(self.add_network, addrs) + list(map(self.add_network, addrs)) def add_network(self, addr): if addr is "": From 17624d0b99731a5947a2df9090039ad531883cc7 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 1 Feb 2015 02:40:48 +0800 Subject: [PATCH 019/104] Fix large file test since forbidden ip list is default to localhost Use an empty forbidden ip list to override default list should work. --- tests/test_large_file.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_large_file.sh b/tests/test_large_file.sh index e8acd79..33bcb59 100755 --- a/tests/test_large_file.sh +++ b/tests/test_large_file.sh @@ -8,7 +8,7 @@ mkdir -p tmp $PYTHON shadowsocks/local.py -c tests/aes.json & LOCAL=$! -$PYTHON shadowsocks/server.py -c tests/aes.json & +$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & SERVER=$! sleep 3 From 453a9c61a6135087b72b247d325157cc67a15921 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 09:09:35 +0800 Subject: [PATCH 020/104] show CIDR error more friendly --- shadowsocks/common.py | 4 ++-- shadowsocks/utils.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 0c4e278..1c10671 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -206,7 +206,7 @@ class IPNetwork(object): hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0])) ip = (hi << 64) | lo else: - raise SyntaxError("Not a valid CIDR notation: %s" % addr) + raise Exception("Not a valid CIDR notation: %s" % addr) if len(block) is 1: prefix_size = 0 while (ip & 1) == 0 and ip is not 0: @@ -218,7 +218,7 @@ class IPNetwork(object): prefix_size = addr_len - int(block[1]) ip >>= prefix_size else: - raise SyntaxError("Not a valid CIDR notation: %s" % addr) + raise Exception("Not a valid CIDR notation: %s" % addr) if addr_family is socket.AF_INET: self._network_list_v4.append((ip, prefix_size)) else: diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 6ea3daa..f791a2a 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -193,8 +193,12 @@ def get_config(is_local): sys.exit(2) else: config['server'] = config.get('server', '0.0.0.0') - config['forbidden_ip'] = \ - IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) + try: + config['forbidden_ip'] = \ + IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) + except Exception as e: + logging.error(e) + sys.exit(2) config['server_port'] = config.get('server_port', 8388) if is_local and not config.get('password', None): From c39bbbe237f230df62c17c751731f64743d92838 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 09:13:29 +0800 Subject: [PATCH 021/104] test invalid CIDR --- tests/test_command.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_command.sh b/tests/test_command.sh index eba4c8c..0995299 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -36,5 +36,7 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password or port_password not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop +assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": Not a valid CIDR notation: 127.0.0.1/4a" +$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop assert_end command From b77f419336ee2f2dd5b3823bdde92124c95d7a94 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 09:23:45 +0800 Subject: [PATCH 022/104] test if localhost is in the default forbidden list --- .jenkins.sh | 6 ++++++ tests/test.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/.jenkins.sh b/.jenkins.sh index f14969f..c67fcdb 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -48,6 +48,12 @@ run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0 run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" run_test python tests/test.py --with-coverage --should-fail --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +# test localhost is in the forbidden list by default +run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" + +# test localhost available when forbidden list is empty +run_test python tests/test.py --with-coverage --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=''" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" + if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then # we have to run it twice: diff --git a/tests/test.py b/tests/test.py index 933027b..5ad94c9 100755 --- a/tests/test.py +++ b/tests/test.py @@ -41,6 +41,7 @@ parser.add_argument('-a', '--client-args', type=str, default=None) parser.add_argument('-b', '--server-args', type=str, default=None) parser.add_argument('--with-coverage', action='store_true', default=None) parser.add_argument('--should-fail', action='store_true', default=None) +parser.add_argument('--tcp-only', action='store_true', default=None) parser.add_argument('--url', type=str, default='http://www.example.com/') parser.add_argument('--dns', type=str, default='8.8.8.8') @@ -127,6 +128,8 @@ try: else: if r != 0: sys.exit(1) + if config.tcp_only: + break p4 = Popen(['socksify', 'dig', '@%s' % config.dns, 'www.google.com'], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) From 4aba904d6ec205f30c187b331a69b4cb8661d5f5 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 09:24:49 +0800 Subject: [PATCH 023/104] fix typo --- .jenkins.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jenkins.sh b/.jenkins.sh index c67fcdb..510732a 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -51,7 +51,7 @@ run_test python tests/test.py --with-coverage --should-fail --url="http://localh # test localhost is in the forbidden list by default run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" -# test localhost available when forbidden list is empty +# test localhost is available when forbidden list is empty run_test python tests/test.py --with-coverage --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=''" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then From 5a7225c54b6cd2f0cbef2566de07f76dc2f64c70 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 09:27:18 +0800 Subject: [PATCH 024/104] fix a problem in test arguments --- .jenkins.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jenkins.sh b/.jenkins.sh index 510732a..8d06b1d 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -52,7 +52,7 @@ run_test python tests/test.py --with-coverage --should-fail --url="http://localh run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" # test localhost is available when forbidden list is empty -run_test python tests/test.py --with-coverage --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=''" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +run_test python tests/test.py --with-coverage --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then From 3b7755bd5ebe451c8070f68696e5477b116d99ec Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 09:38:32 +0800 Subject: [PATCH 025/104] fix a travis issue --- .jenkins.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.jenkins.sh b/.jenkins.sh index 8d06b1d..73c4abc 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -46,13 +46,13 @@ run_test python tests/test.py --with-coverage -c tests/workers.json run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv" run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" -run_test python tests/test.py --with-coverage --should-fail --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" # test localhost is in the forbidden list by default -run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" # test localhost is available when forbidden list is empty -run_test python tests/test.py --with-coverage --tcp-only --url="http://localhost/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +run_test python tests/test.py --with-coverage --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then From da65d0a2eee60ee98f3dd9d58971c354695f5816 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 1 Feb 2015 17:15:10 +0800 Subject: [PATCH 026/104] update coverage_server --- tests/coverage_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/coverage_server.py b/tests/coverage_server.py index 2df55e3..d752cff 100644 --- a/tests/coverage_server.py +++ b/tests/coverage_server.py @@ -11,7 +11,7 @@ if __name__ == '__main__': with open('/tmp/%s-coverage' % project, 'rb') as f: coverage = f.read().strip() n = int(coverage.strip('%')) - if n > 80: + if n >= 80: color = 'brightgreen' else: color = 'yellow' From ae99698b4e2df939b95dbb58b5e748ce85cfa1f0 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 15:38:55 +0800 Subject: [PATCH 027/104] implement --user fix @278 --- shadowsocks/daemon.py | 32 ++++++++++++++++++++++++++++++++ shadowsocks/local.py | 1 + shadowsocks/server.py | 2 ++ shadowsocks/utils.py | 22 +++++++++++++--------- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index d206ccf..3d2b448 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -183,3 +183,35 @@ def daemon_stop(pid_file): sys.exit(1) print('stopped') os.unlink(pid_file) + + +def set_user(username): + if username is None: + return + + import pwd + import grp + + try: + pwrec = pwd.getpwnam(username) + except KeyError: + logging.error('user not found: %s' % username) + raise + user = pwrec[0] + uid = pwrec[2] + gid = pwrec[3] + + cur_uid = os.getuid() + if uid == cur_uid: + return + if cur_uid != 0: + logging.error('can not set user as nonroot user') + # will raise later + + # inspired by supervisor + if hasattr(os, 'setgroups'): + groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]] + groups.insert(0, gid) + os.setgroups(groups) + os.setgid(gid) + os.setuid(uid) diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 994b6d8..87c5215 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -73,6 +73,7 @@ def main(): sys.exit(1) signal.signal(signal.SIGINT, int_handler) + daemon.set_user(config.get('user', None)) loop.run() except (KeyboardInterrupt, IOError, OSError) as e: logging.error(e) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 8eed4ad..345ec0b 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -86,6 +86,8 @@ def main(): loop = eventloop.EventLoop() dns_resolver.add_to_loop(loop) list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers)) + + daemon.set_user(config.get('user', None)) loop.run() except (KeyboardInterrupt, IOError, OSError) as e: logging.error(e) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index f791a2a..b12f54d 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -89,7 +89,11 @@ def check_config(config): if config.get('password') in [b'mypassword']: logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 'config.json!') - exit(1) + sys.exit(1) + if config.get('user', None) is not None: + if os.name != 'posix': + logging.error('user can be used only on Unix') + sys.exit(1) def get_config(is_local): @@ -97,11 +101,11 @@ def get_config(is_local): format='%(levelname)-s: %(message)s') if is_local: shortopts = 'hd:s:b:p:k:l:m:c:t:vq' - longopts = ['help', 'fast-open', 'pid-file=', 'log-file='] + longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user='] else: shortopts = 'hd:s:p:k:m:c:t:vq' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip='] + 'forbidden-ip=', 'user='] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -147,6 +151,8 @@ def get_config(is_local): config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) + elif key == '--user': + config['user'] = to_str(value) elif key == '--forbidden-ip': config['forbidden_ip'] = to_str(value).split(',') elif key in ('-h', '--help'): @@ -247,9 +253,7 @@ def print_help(is_local): def print_local_help(): - print('''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT] - [-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD] - [-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] -[d] [-q] + print('''usage: sslocal [OPTION]... A fast tunnel proxy that helps you bypass firewalls. You can supply configurations via either config file or command line arguments. @@ -270,6 +274,7 @@ General options: -d start/stop/restart daemon mode --pid-file PID_FILE pid file for daemon mode --log-file LOG_FILE log file for daemon mode + --user USER username to run as -v, -vv verbose mode -q, -qq quiet mode, only show warnings/errors @@ -278,9 +283,7 @@ Online help: def print_server_help(): - print('''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD - -m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open] - [--workers WORKERS] [-v] [-d start] [-q] + print('''usage: ssserver [OPTION]... A fast tunnel proxy that helps you bypass firewalls. You can supply configurations via either config file or command line arguments. @@ -301,6 +304,7 @@ General options: -d start/stop/restart daemon mode --pid-file PID_FILE pid file for daemon mode --log-file LOG_FILE log file for daemon mode + --user USER username to run as -v, -vv verbose mode -q, -qq quiet mode, only show warnings/errors From 7aa37cad8e3360fad28a2c95eaa577d94adc8070 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 15:46:22 +0800 Subject: [PATCH 028/104] check cipher before daemon start fix #280 --- README.md | 13 ++++++++++--- shadowsocks/local.py | 5 +---- shadowsocks/server.py | 4 +--- shadowsocks/utils.py | 3 +++ 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index aba545b..24b7ed1 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,19 @@ See [Install Server on Windows] ### Usage - ssserver -p 8000 -k password -m rc4-md5 + ssserver -p 443 -k password -m rc4-md5 To run in the background: - ssserver -p 8000 -k password -m rc4-md5 -d start - ssserver -p 8000 -k password -m rc4-md5 -d stop + sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start + +To stop: + + sudo ssserver -d stop + +To check the log: + + sudo less /var/log/shadowsocks.log Check all the options via `-h`. You can also use a [Configuration] file instead. diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 87c5215..3cf3da7 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -30,8 +30,7 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import utils, daemon, encrypt, eventloop, tcprelay, udprelay,\ - asyncdns +from shadowsocks import utils, daemon, eventloop, tcprelay, udprelay, asyncdns def main(): @@ -49,8 +48,6 @@ def main(): utils.print_shadowsocks() - encrypt.try_cipher(config['password'], config['method']) - try: logging.info("starting local at %s:%d" % (config['local_address'], config['local_port'])) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 345ec0b..eca43ee 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -30,8 +30,7 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import utils, daemon, encrypt, eventloop, tcprelay, udprelay,\ - asyncdns +from shadowsocks import utils, daemon, eventloop, tcprelay, udprelay, asyncdns def main(): @@ -57,7 +56,6 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - encrypt.try_cipher(config['password'], config['method']) tcp_servers = [] udp_servers = [] dns_resolver = asyncdns.DNSResolver() diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index b12f54d..0e50089 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -30,6 +30,7 @@ import sys import getopt import logging from shadowsocks.common import to_bytes, to_str, IPNetwork +from shadowsocks import encrypt VERBOSE_LEVEL = 5 @@ -95,6 +96,8 @@ def check_config(config): logging.error('user can be used only on Unix') sys.exit(1) + encrypt.try_cipher(config['password'], config['method']) + def get_config(is_local): logging.basicConfig(level=logging.INFO, From 73f21ffbf61258afb8d38dc0a4d5bdfca0b0cc67 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 15:50:41 +0800 Subject: [PATCH 029/104] do not check config for daemon stop --- shadowsocks/daemon.py | 3 --- shadowsocks/utils.py | 10 +++++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index 3d2b448..9961ea7 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -43,9 +43,6 @@ def daemon_exec(config): command = 'start' pid_file = config['pid-file'] log_file = config['log-file'] - command = common.to_str(command) - pid_file = common.to_str(pid_file) - log_file = common.to_str(log_file) if command == 'start': daemon_start(pid_file, log_file) elif command == 'stop': diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 0e50089..3e2fb57 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -70,6 +70,10 @@ def find_config(): def check_config(config): + if config.get('daemon', None) == 'stop': + # no need to specify configuration for daemon stop + return + if config.get('local_address', '') in [b'0.0.0.0']: logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe') if config.get('server', '') in [b'127.0.0.1', b'localhost']: @@ -165,11 +169,11 @@ def get_config(is_local): print_server_help() sys.exit(0) elif key == '-d': - config['daemon'] = value + config['daemon'] = to_str(value) elif key == '--pid-file': - config['pid-file'] = value + config['pid-file'] = to_str(value) elif key == '--log-file': - config['log-file'] = value + config['log-file'] = to_str(value) elif key == '-q': v_count -= 1 config['verbose'] = v_count From 1c814654367f1ac5afe7bd23439f6d57792e30d7 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 15:57:44 +0800 Subject: [PATCH 030/104] fix command line --- shadowsocks/utils.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 3e2fb57..417348c 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -69,11 +69,28 @@ def find_config(): return None -def check_config(config): +def check_config(config, is_local): if config.get('daemon', None) == 'stop': # no need to specify configuration for daemon stop return + if is_local and not config.get('password', None): + logging.error('password not specified') + print_help(is_local) + sys.exit(2) + + if not is_local and not config.get('password', None) \ + and not config.get('port_password', None): + logging.error('password or port_password not specified') + print_help(is_local) + sys.exit(2) + + if 'local_port' in config: + config['local_port'] = int(config['local_port']) + + if 'server_port' in config and type(config['server_port']) != list: + config['server_port'] = int(config['server_port']) + if config.get('local_address', '') in [b'0.0.0.0']: logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe') if config.get('server', '') in [b'127.0.0.1', b'localhost']: @@ -214,23 +231,6 @@ def get_config(is_local): sys.exit(2) config['server_port'] = config.get('server_port', 8388) - if is_local and not config.get('password', None): - logging.error('password not specified') - print_help(is_local) - sys.exit(2) - - if not is_local and not config.get('password', None) \ - and not config.get('port_password', None): - logging.error('password or port_password not specified') - print_help(is_local) - sys.exit(2) - - if 'local_port' in config: - config['local_port'] = int(config['local_port']) - - if 'server_port' in config and type(config['server_port']) != list: - config['server_port'] = int(config['server_port']) - logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') if config['verbose'] >= 2: @@ -247,7 +247,7 @@ def get_config(is_local): format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') - check_config(config) + check_config(config, is_local) return config From fbf15cb94201a9f3279d5234c00436b9f2166fdd Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 15:59:44 +0800 Subject: [PATCH 031/104] update command tests --- tests/test_command.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_command.sh b/tests/test_command.sh index 0995299..2b8b68a 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -30,10 +30,10 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password not specified" +assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password or port_password not specified" +assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password or port_password not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": Not a valid CIDR notation: 127.0.0.1/4a" From 0cd261dd109ab86ce6842dcd46692cb115c16145 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 16:22:56 +0800 Subject: [PATCH 032/104] catch KeyError --- shadowsocks/local.py | 4 ++-- shadowsocks/server.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 3cf3da7..c1a84f1 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -72,12 +72,12 @@ def main(): daemon.set_user(config.get('user', None)) loop.run() - except (KeyboardInterrupt, IOError, OSError) as e: + except Exception as e: logging.error(e) if config['verbose']: import traceback traceback.print_exc() - os._exit(1) + sys.exit(1) if __name__ == '__main__': main() diff --git a/shadowsocks/server.py b/shadowsocks/server.py index eca43ee..41c70ca 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -87,12 +87,12 @@ def main(): daemon.set_user(config.get('user', None)) loop.run() - except (KeyboardInterrupt, IOError, OSError) as e: + except Exception as e: logging.error(e) if config['verbose']: import traceback traceback.print_exc() - os._exit(1) + sys.exit(1) if int(config['workers']) > 1: if os.name == 'posix': From 27a0c7754d335725b478e0fe5a3019b52fa6d024 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 16:23:17 +0800 Subject: [PATCH 033/104] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33cc44c..ff9ac69 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.6", + version="2.6.7", license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 2c59ffb952efab2ff16f28a3bba5217e8611fa05 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 16:42:37 +0800 Subject: [PATCH 034/104] update rst --- README.rst | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 510b233..40c9ad1 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,6 @@ Install ~~~~~~~ Debian / Ubuntu: -^^^^^^^^^^^^^^^^ :: @@ -20,7 +19,6 @@ Debian / Ubuntu: pip install shadowsocks CentOS: -^^^^^^^ :: @@ -28,7 +26,6 @@ CentOS: pip install shadowsocks Windows: -^^^^^^^^ See `Install Server on Windows `__ @@ -38,14 +35,25 @@ Usage :: - ssserver -p 8000 -k password -m rc4-md5 + ssserver -p 443 -k password -m rc4-md5 To run in the background: :: - ssserver -p 8000 -k password -m rc4-md5 -d start - ssserver -p 8000 -k password -m rc4-md5 -d stop + sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start + +To stop: + +:: + + sudo ssserver -d stop + +To check the log: + +:: + + sudo less /var/log/shadowsocks.log Check all the options via ``-h``. You can also use a `Configuration `__ @@ -88,4 +96,4 @@ Bugs and Issues .. |Build Status| image:: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat :target: https://travis-ci.org/shadowsocks/shadowsocks .. |Coverage Status| image:: https://jenkins.shadowvpn.org/result/shadowsocks - :target: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/htmlcov/index.html + :target: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html From 96a86c028df463b8a051711111b486d544415b3b Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 2 Feb 2015 17:29:57 +0800 Subject: [PATCH 035/104] update CHANGES --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 6e0331f..8004a3f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +2.6.7 2015-02-02 +- Support --user +- Support CIDR format in --forbidden-ip +- Minor fixes + 2.6.6 2015-01-23 - Fix a crash in forbidden list From ce805f0aeaea03646e01b623c4e2185f63a3562f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 3 Feb 2015 14:10:36 +0800 Subject: [PATCH 036/104] license under Apache License v2.0 --- LICENSE | 215 ++++++++++++++++++++++++++++++--- README.md | 15 ++- README.rst | 16 ++- setup.py | 6 +- shadowsocks/__init__.py | 28 ++--- shadowsocks/asyncdns.py | 28 ++--- shadowsocks/common.py | 28 ++--- shadowsocks/crypto/__init__.py | 28 ++--- shadowsocks/crypto/openssl.py | 28 ++--- shadowsocks/crypto/rc4_md5.py | 28 ++--- shadowsocks/crypto/sodium.py | 28 ++--- shadowsocks/crypto/table.py | 28 ++--- shadowsocks/crypto/util.py | 28 ++--- shadowsocks/daemon.py | 28 ++--- shadowsocks/encrypt.py | 28 ++--- shadowsocks/eventloop.py | 28 ++--- shadowsocks/local.py | 28 ++--- shadowsocks/lru_cache.py | 28 ++--- shadowsocks/server.py | 28 ++--- shadowsocks/tcprelay.py | 28 ++--- shadowsocks/udprelay.py | 28 ++--- shadowsocks/utils.py | 28 ++--- tests/coverage_server.py | 14 +++ tests/nose_plugin.py | 16 +++ tests/test.py | 28 ++--- 25 files changed, 469 insertions(+), 345 deletions(-) diff --git a/LICENSE b/LICENSE index 76886fa..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,202 @@ -Shadowsocks -Copyright (c) 2012-2015 clowwindy + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + 1. Definitions. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 24b7ed1..aee2bdc 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,20 @@ You can find all the documentation in the [Wiki]. License ------- -MIT + +Copyright 2015 clowwindy + +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. Bugs and Issues ---------------- diff --git a/README.rst b/README.rst index 40c9ad1..bf2a3ec 100644 --- a/README.rst +++ b/README.rst @@ -81,7 +81,21 @@ You can find all the documentation in the License ------- -MIT +Copyright 2015 clowwindy + +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at + +:: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. Bugs and Issues --------------- diff --git a/setup.py b/setup.py index ff9ac69..9485ce2 100644 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.7", - license='MIT', + version="2.6.8", + license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', author_email='clowwindy42@gmail.com', @@ -24,7 +24,7 @@ setup( ssserver = shadowsocks.server:main """, classifiers=[ - 'License :: OSI Approved :: MIT License', + 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', diff --git a/shadowsocks/__init__.py b/shadowsocks/__init__.py index 5ba5908..dc3abd4 100644 --- a/shadowsocks/__init__.py +++ b/shadowsocks/__init__.py @@ -1,24 +1,18 @@ #!/usr/bin/python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2012-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 6fee6b9..73fab9c 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -1,25 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2014-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 1c10671..1977dcd 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2013-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/__init__.py b/shadowsocks/crypto/__init__.py index 6251321..401c7b7 100644 --- a/shadowsocks/crypto/__init__.py +++ b/shadowsocks/crypto/__init__.py @@ -1,24 +1,18 @@ #!/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index bb11627..d801730 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -1,24 +1,18 @@ #!/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 33d481d..40c344d 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -1,24 +1,18 @@ #!/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index 74fbb33..0c1b2f1 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -1,24 +1,18 @@ #!/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/table.py b/shadowsocks/crypto/table.py index 08c1205..555fd0a 100644 --- a/shadowsocks/crypto/table.py +++ b/shadowsocks/crypto/table.py @@ -1,24 +1,18 @@ # !/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index 1242f8e..e579455 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -1,24 +1,18 @@ #!/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index 9961ea7..a73e927 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2014-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index 3dd9264..9365a98 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -1,24 +1,18 @@ #!/usr/bin/env python - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2012-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 304b229..839842d 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2013-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. # from ssloop # https://github.com/clowwindy/ssloop diff --git a/shadowsocks/local.py b/shadowsocks/local.py index c1a84f1..9520c68 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -1,25 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2012-2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/lru_cache.py b/shadowsocks/lru_cache.py index 4523399..27c0738 100644 --- a/shadowsocks/lru_cache.py +++ b/shadowsocks/lru_cache.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 41c70ca..195f17b 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -1,25 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index d68e424..03cd9b5 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2015 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index ff6391c..9e5463e 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. # SOCKS5 UDP Request # +----+------+------+----------+----------+----------+ diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 417348c..4a462f8 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/tests/coverage_server.py b/tests/coverage_server.py index d752cff..23cc8cd 100644 --- a/tests/coverage_server.py +++ b/tests/coverage_server.py @@ -1,4 +1,18 @@ #!/usr/bin/env python +# +# Copyright 2015 clowwindy +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. if __name__ == '__main__': import tornado.ioloop diff --git a/tests/nose_plugin.py b/tests/nose_plugin.py index ad32cf0..86b1a86 100644 --- a/tests/nose_plugin.py +++ b/tests/nose_plugin.py @@ -1,3 +1,19 @@ +#!/usr/bin/env python +# +# Copyright 2015 clowwindy +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + import nose from nose.plugins.base import Plugin diff --git a/tests/test.py b/tests/test.py index 5ad94c9..4953476 100755 --- a/tests/test.py +++ b/tests/test.py @@ -1,25 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright (c) 2014 clowwindy # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# Copyright 2015 clowwindy # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. from __future__ import absolute_import, division, print_function, \ with_statement From 318d88ec89d39d8b5ef458861c6beaab2ddb107a Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 3 Feb 2015 18:09:07 +0800 Subject: [PATCH 037/104] use string more --- shadowsocks/crypto/openssl.py | 64 +++++++++++++++++------------------ shadowsocks/crypto/rc4_md5.py | 6 ++-- shadowsocks/crypto/sodium.py | 16 ++++----- shadowsocks/crypto/table.py | 6 ++-- shadowsocks/encrypt.py | 18 +++++----- shadowsocks/utils.py | 6 ++-- 6 files changed, 57 insertions(+), 59 deletions(-) diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index d801730..d2216c8 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -111,31 +111,31 @@ class OpenSSLCrypto(object): ciphers = { - b'aes-128-cfb': (16, 16, OpenSSLCrypto), - b'aes-192-cfb': (24, 16, OpenSSLCrypto), - b'aes-256-cfb': (32, 16, OpenSSLCrypto), - b'aes-128-ofb': (16, 16, OpenSSLCrypto), - b'aes-192-ofb': (24, 16, OpenSSLCrypto), - b'aes-256-ofb': (32, 16, OpenSSLCrypto), - b'aes-128-ctr': (16, 16, OpenSSLCrypto), - b'aes-192-ctr': (24, 16, OpenSSLCrypto), - b'aes-256-ctr': (32, 16, OpenSSLCrypto), - b'aes-128-cfb8': (16, 16, OpenSSLCrypto), - b'aes-192-cfb8': (24, 16, OpenSSLCrypto), - b'aes-256-cfb8': (32, 16, OpenSSLCrypto), - b'aes-128-cfb1': (16, 16, OpenSSLCrypto), - b'aes-192-cfb1': (24, 16, OpenSSLCrypto), - b'aes-256-cfb1': (32, 16, OpenSSLCrypto), - b'bf-cfb': (16, 8, OpenSSLCrypto), - b'camellia-128-cfb': (16, 16, OpenSSLCrypto), - b'camellia-192-cfb': (24, 16, OpenSSLCrypto), - b'camellia-256-cfb': (32, 16, OpenSSLCrypto), - b'cast5-cfb': (16, 8, OpenSSLCrypto), - b'des-cfb': (8, 8, OpenSSLCrypto), - b'idea-cfb': (16, 8, OpenSSLCrypto), - b'rc2-cfb': (16, 8, OpenSSLCrypto), - b'rc4': (16, 0, OpenSSLCrypto), - b'seed-cfb': (16, 16, OpenSSLCrypto), + 'aes-128-cfb': (16, 16, OpenSSLCrypto), + 'aes-192-cfb': (24, 16, OpenSSLCrypto), + 'aes-256-cfb': (32, 16, OpenSSLCrypto), + 'aes-128-ofb': (16, 16, OpenSSLCrypto), + 'aes-192-ofb': (24, 16, OpenSSLCrypto), + 'aes-256-ofb': (32, 16, OpenSSLCrypto), + 'aes-128-ctr': (16, 16, OpenSSLCrypto), + 'aes-192-ctr': (24, 16, OpenSSLCrypto), + 'aes-256-ctr': (32, 16, OpenSSLCrypto), + 'aes-128-cfb8': (16, 16, OpenSSLCrypto), + 'aes-192-cfb8': (24, 16, OpenSSLCrypto), + 'aes-256-cfb8': (32, 16, OpenSSLCrypto), + 'aes-128-cfb1': (16, 16, OpenSSLCrypto), + 'aes-192-cfb1': (24, 16, OpenSSLCrypto), + 'aes-256-cfb1': (32, 16, OpenSSLCrypto), + 'bf-cfb': (16, 8, OpenSSLCrypto), + 'camellia-128-cfb': (16, 16, OpenSSLCrypto), + 'camellia-192-cfb': (24, 16, OpenSSLCrypto), + 'camellia-256-cfb': (32, 16, OpenSSLCrypto), + 'cast5-cfb': (16, 8, OpenSSLCrypto), + 'des-cfb': (8, 8, OpenSSLCrypto), + 'idea-cfb': (16, 8, OpenSSLCrypto), + 'rc2-cfb': (16, 8, OpenSSLCrypto), + 'rc4': (16, 0, OpenSSLCrypto), + 'seed-cfb': (16, 16, OpenSSLCrypto), } @@ -148,31 +148,31 @@ def run_method(method): def test_aes_128_cfb(): - run_method(b'aes-128-cfb') + run_method('aes-128-cfb') def test_aes_256_cfb(): - run_method(b'aes-256-cfb') + run_method('aes-256-cfb') def test_aes_128_cfb8(): - run_method(b'aes-128-cfb8') + run_method('aes-128-cfb8') def test_aes_256_ofb(): - run_method(b'aes-256-ofb') + run_method('aes-256-ofb') def test_aes_256_ctr(): - run_method(b'aes-256-ctr') + run_method('aes-256-ctr') def test_bf_cfb(): - run_method(b'bf-cfb') + run_method('bf-cfb') def test_rc4(): - run_method(b'rc4') + run_method('rc4') if __name__ == '__main__': diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 40c344d..1f07a82 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -34,15 +34,15 @@ def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, ciphers = { - b'rc4-md5': (16, 16, create_cipher), + 'rc4-md5': (16, 16, create_cipher), } def test(): from shadowsocks.crypto import util - cipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 1) - decipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 0) + cipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 1) + decipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index 0c1b2f1..ae86fef 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -64,9 +64,9 @@ class SodiumCrypto(object): self.iv = iv self.key_ptr = c_char_p(key) self.iv_ptr = c_char_p(iv) - if cipher_name == b'salsa20': + if cipher_name == 'salsa20': self.cipher = libsodium.crypto_stream_salsa20_xor_ic - elif cipher_name == b'chacha20': + elif cipher_name == 'chacha20': self.cipher = libsodium.crypto_stream_chacha20_xor_ic else: raise Exception('Unknown cipher') @@ -95,22 +95,22 @@ class SodiumCrypto(object): ciphers = { - b'salsa20': (32, 8, SodiumCrypto), - b'chacha20': (32, 8, SodiumCrypto), + 'salsa20': (32, 8, SodiumCrypto), + 'chacha20': (32, 8, SodiumCrypto), } def test_salsa20(): - cipher = SodiumCrypto(b'salsa20', b'k' * 32, b'i' * 16, 1) - decipher = SodiumCrypto(b'salsa20', b'k' * 32, b'i' * 16, 0) + cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1) + decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) def test_chacha20(): - cipher = SodiumCrypto(b'chacha20', b'k' * 32, b'i' * 16, 1) - decipher = SodiumCrypto(b'chacha20', b'k' * 32, b'i' * 16, 0) + cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) + decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/table.py b/shadowsocks/crypto/table.py index 555fd0a..bc693f5 100644 --- a/shadowsocks/crypto/table.py +++ b/shadowsocks/crypto/table.py @@ -67,7 +67,7 @@ class TableCipher(object): ciphers = { - b'table': (0, 0, TableCipher) + 'table': (0, 0, TableCipher) } @@ -163,8 +163,8 @@ def test_table_result(): def test_encryption(): from shadowsocks.crypto import util - cipher = TableCipher(b'table', b'test', b'', 1) - decipher = TableCipher(b'table', b'test', b'', 0) + cipher = TableCipher('table', b'test', b'', 1) + decipher = TableCipher('table', b'test', b'', 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index 9365a98..4e87f41 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -22,6 +22,7 @@ import sys import hashlib import logging +from shadowsocks import common from shadowsocks.crypto import rc4_md5, openssl, sodium, table @@ -46,8 +47,6 @@ def try_cipher(key, method=None): def EVP_BytesToKey(password, key_len, iv_len): # equivalent to OpenSSL's EVP_BytesToKey() with count 1 # so that we make the same key and iv as nodejs version - if hasattr(password, 'encode'): - password = password.encode('utf-8') cached_key = '%s-%d-%d' % (password, key_len, iv_len) r = cached_keys.get(cached_key, None) if r: @@ -95,8 +94,7 @@ class Encryptor(object): return len(self.cipher_iv) def get_cipher(self, password, method, op, iv): - if hasattr(password, 'encode'): - password = password.encode('utf-8') + password = common.to_bytes(password) m = self._method_info if m[0] > 0: key, iv_ = EVP_BytesToKey(password, m[0], m[1]) @@ -153,12 +151,12 @@ def encrypt_all(password, method, op, data): CIPHERS_TO_TEST = [ - b'aes-128-cfb', - b'aes-256-cfb', - b'rc4-md5', - b'salsa20', - b'chacha20', - b'table', + 'aes-128-cfb', + 'aes-256-cfb', + 'rc4-md5', + 'salsa20', + 'chacha20', + 'table', ] diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 4a462f8..c261148 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -154,11 +154,11 @@ def get_config(is_local): elif key == '-l': config['local_port'] = int(value) elif key == '-s': - config['server'] = to_bytes(value) + config['server'] = to_str(value) elif key == '-m': - config['method'] = to_bytes(value) + config['method'] = to_str(value) elif key == '-b': - config['local_address'] = to_bytes(value) + config['local_address'] = to_str(value) elif key == '-v': v_count += 1 # '-vv' turns on more verbose mode From 6d09cd21ca6acd506a42858b123ece1ab4e6d423 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 3 Feb 2015 18:13:01 +0800 Subject: [PATCH 038/104] fix openssl --- shadowsocks/crypto/openssl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index d2216c8..3775b6c 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -20,6 +20,7 @@ from __future__ import absolute_import, division, print_function, \ from ctypes import c_char_p, c_int, c_long, byref,\ create_string_buffer, c_void_p +from shadowsocks import common from shadowsocks.crypto import util __all__ = ['ciphers'] @@ -58,7 +59,7 @@ def load_openssl(): def load_cipher(cipher_name): - func_name = b'EVP_' + cipher_name.replace(b'-', b'_') + func_name = 'EVP_' + cipher_name.replace('-', '_') if bytes != str: func_name = str(func_name, 'utf-8') cipher = getattr(libcrypto, func_name, None) @@ -73,6 +74,7 @@ class OpenSSLCrypto(object): self._ctx = None if not loaded: load_openssl() + cipher_name = common.to_bytes(cipher_name) cipher = libcrypto.EVP_get_cipherbyname(cipher_name) if not cipher: cipher = load_cipher(cipher_name) From e564f17505f6e935b7893b42d357aae672942646 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 3 Feb 2015 18:23:22 +0800 Subject: [PATCH 039/104] fix bytes in utils --- shadowsocks/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index c261148..24e9b46 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -90,10 +90,10 @@ def check_config(config, is_local): if config.get('server', '') in [b'127.0.0.1', b'localhost']: logging.warn('warning: server set to listen on %s:%s, are you sure?' % (to_str(config['server']), config['server_port'])) - if (config.get('method', '') or '').lower() == b'table': + if (config.get('method', '') or '').lower() == 'table': logging.warn('warning: table is not safe; please use a safer cipher, ' 'like AES-256-CFB') - if (config.get('method', '') or '').lower() == b'rc4': + if (config.get('method', '') or '').lower() == 'rc4': logging.warn('warning: RC4 is not safe; please use a safer cipher, ' 'like AES-256-CFB') if config.get('timeout', 300) < 100: @@ -198,8 +198,8 @@ def get_config(is_local): print_help(is_local) sys.exit(2) - config['password'] = config.get('password', '') - config['method'] = config.get('method', b'aes-256-cfb') + config['password'] = to_bytes(config.get('password', b'')) + config['method'] = to_str(config.get('method', 'aes-256-cfb')) config['port_password'] = config.get('port_password', None) config['timeout'] = int(config.get('timeout', 300)) config['fast_open'] = config.get('fast_open', False) @@ -208,7 +208,7 @@ def get_config(is_local): config['log-file'] = config.get('log-file', '/var/log/shadowsocks.log') config['workers'] = config.get('workers', 1) config['verbose'] = config.get('verbose', False) - config['local_address'] = config.get('local_address', '127.0.0.1') + config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) config['local_port'] = config.get('local_port', 1080) if is_local: if config.get('server', None) is None: From e71ce6c758643f06c05de06d5dd10cb7fe205378 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 3 Feb 2015 18:45:36 +0800 Subject: [PATCH 040/104] fix server check --- shadowsocks/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 24e9b46..96abdb6 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -87,7 +87,7 @@ def check_config(config, is_local): if config.get('local_address', '') in [b'0.0.0.0']: logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe') - if config.get('server', '') in [b'127.0.0.1', b'localhost']: + if config.get('server', '') in ['127.0.0.1', 'localhost']: logging.warn('warning: server set to listen on %s:%s, are you sure?' % (to_str(config['server']), config['server_port'])) if (config.get('method', '') or '').lower() == 'table': @@ -215,8 +215,10 @@ def get_config(is_local): logging.error('server addr not specified') print_local_help() sys.exit(2) + else: + config['server'] = to_str(config['server']) else: - config['server'] = config.get('server', '0.0.0.0') + config['server'] = to_str(config.get('server', '0.0.0.0')) try: config['forbidden_ip'] = \ IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) From 783a6ef7f294b71e22b4d485545b4a7c32a651c6 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 9 Feb 2015 13:50:46 +0800 Subject: [PATCH 041/104] support multiple server ip on client side --- .jenkins.sh | 1 + shadowsocks/tcprelay.py | 3 ++- shadowsocks/udprelay.py | 3 ++- tests/client-multi-server-ip.json | 10 ++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/client-multi-server-ip.json diff --git a/.jenkins.sh b/.jenkins.sh index 73c4abc..a811983 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -41,6 +41,7 @@ run_test python tests/test.py --with-coverage -c tests/salsa20.json run_test python tests/test.py --with-coverage -c tests/chacha20.json run_test python tests/test.py --with-coverage -c tests/table.json run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json +run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client-multi-server-ip.json run_test python tests/test.py --with-coverage -s tests/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json run_test python tests/test.py --with-coverage -c tests/workers.json run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 03cd9b5..f65b282 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -144,8 +144,9 @@ class TCPRelayHandler(object): server_port = self._config['server_port'] if type(server_port) == list: server_port = random.choice(server_port) + if type(server) == list: + server = random.choice(server) logging.debug('chosen server: %s:%d', server, server_port) - # TODO support multiple server IP return server, server_port def _update_activity(self): diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 9e5463e..0499f0e 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -127,8 +127,9 @@ class UDPRelay(object): server_port = self._config['server_port'] if type(server_port) == list: server_port = random.choice(server_port) + if type(server) == list: + server = random.choice(server) logging.debug('chosen server: %s:%d', server, server_port) - # TODO support multiple server IP return server, server_port def _close_client(self, client): diff --git a/tests/client-multi-server-ip.json b/tests/client-multi-server-ip.json new file mode 100644 index 0000000..1823c2a --- /dev/null +++ b/tests/client-multi-server-ip.json @@ -0,0 +1,10 @@ +{ + "server":["127.0.0.1", "127.0.0.1"], + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} From dfd81af8445a4d9768cae45246de1c35102013ac Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 9 Feb 2015 14:43:11 +0800 Subject: [PATCH 042/104] support --version --- .jenkins.sh | 2 +- shadowsocks/local.py | 2 -- shadowsocks/server.py | 2 -- shadowsocks/tcprelay.py | 2 +- shadowsocks/utils.py | 16 +++++++++++----- tests/test_command.sh | 3 +++ 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.jenkins.sh b/.jenkins.sh index a811983..2cd02a2 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -27,7 +27,7 @@ function run_test { python --version coverage erase mkdir tmp -run_test pep8 . +run_test pep8 --ignore=E402 . run_test pyflakes . run_test coverage run tests/nose_plugin.py -v run_test python setup.py sdist diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 9520c68..b75fe9b 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -40,8 +40,6 @@ def main(): daemon.daemon_exec(config) - utils.print_shadowsocks() - try: logging.info("starting local at %s:%d" % (config['local_address'], config['local_port'])) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 195f17b..34aab2a 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -34,8 +34,6 @@ def main(): daemon.daemon_exec(config) - utils.print_shadowsocks() - if config['port_password']: if config['password']: logging.warn('warning: port_password should not be used with ' diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index f65b282..12792f7 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -328,7 +328,7 @@ class TCPRelayHandler(object): addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) if len(addrs) == 0: - raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) + raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) af, socktype, proto, canonname, sa = addrs[0] if self._forbidden_iplist: if common.to_str(sa[0]) in self._forbidden_iplist: diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 96abdb6..71c6d94 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -50,7 +50,7 @@ def print_shadowsocks(): version = pkg_resources.get_distribution('shadowsocks').version except Exception: pass - print('shadowsocks %s' % version) + print('Shadowsocks %s' % version) def find_config(): @@ -119,11 +119,12 @@ def get_config(is_local): format='%(levelname)-s: %(message)s') if is_local: shortopts = 'hd:s:b:p:k:l:m:c:t:vq' - longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user='] + longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=', + 'version'] else: shortopts = 'hd:s:p:k:m:c:t:vq' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user='] + 'forbidden-ip=', 'user=', 'version'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -179,6 +180,9 @@ def get_config(is_local): else: print_server_help() sys.exit(0) + elif key == '--version': + print_shadowsocks() + sys.exit(0) elif key == '-d': config['daemon'] = to_str(value) elif key == '--pid-file': @@ -262,7 +266,6 @@ A fast tunnel proxy that helps you bypass firewalls. You can supply configurations via either config file or command line arguments. Proxy options: - -h, --help show this help message and exit -c CONFIG path to config file -s SERVER_ADDR server address -p SERVER_PORT server port, default: 8388 @@ -274,12 +277,14 @@ Proxy options: --fast-open use TCP_FASTOPEN, requires Linux 3.7+ General options: + -h, --help show this help message and exit -d start/stop/restart daemon mode --pid-file PID_FILE pid file for daemon mode --log-file LOG_FILE log file for daemon mode --user USER username to run as -v, -vv verbose mode -q, -qq quiet mode, only show warnings/errors + --version show version information Online help: ''') @@ -292,7 +297,6 @@ A fast tunnel proxy that helps you bypass firewalls. You can supply configurations via either config file or command line arguments. Proxy options: - -h, --help show this help message and exit -c CONFIG path to config file -s SERVER_ADDR server address, default: 0.0.0.0 -p SERVER_PORT server port, default: 8388 @@ -304,12 +308,14 @@ Proxy options: --forbidden-ip IPLIST comma seperated IP list forbidden to connect General options: + -h, --help show this help message and exit -d start/stop/restart daemon mode --pid-file PID_FILE pid file for daemon mode --log-file LOG_FILE log file for daemon mode --user USER username to run as -v, -vv verbose mode -q, -qq quiet mode, only show warnings/errors + --version show version information Online help: ''') diff --git a/tests/test_command.sh b/tests/test_command.sh index 2b8b68a..be05704 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -6,6 +6,9 @@ PYTHON="coverage run -a -p" LOCAL="$PYTHON shadowsocks/local.py" SERVER="$PYTHON shadowsocks/server.py" +assert "$LOCAL --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" +assert "$SERVER --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" + assert "$LOCAL 2>&1 | grep ERROR" "ERROR: config not specified" assert "$LOCAL 2>&1 | grep usage | cut -d: -f1" "usage" From 48ddc1714ba774d34073e0e34c7e662583365b83 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 17:02:31 +0800 Subject: [PATCH 043/104] move jenkins.sh --- .travis.yml | 2 +- .jenkins.sh => tests/jenkins.sh | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename .jenkins.sh => tests/jenkins.sh (100%) diff --git a/.travis.yml b/.travis.yml index 7a222ea..c5caa33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,4 @@ before_install: - sudo tests/libsodium/install.sh - sudo tests/setup_tc.sh script: - - ./.jenkins.sh + - tests/jenkins.sh diff --git a/.jenkins.sh b/tests/jenkins.sh similarity index 100% rename from .jenkins.sh rename to tests/jenkins.sh From cb7062e1c1bcbec1b2a699a016a774e4d757389b Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 17:16:24 +0800 Subject: [PATCH 044/104] implement utils.print_exception() Previously we used logging.error(e) and traceback.print_exc() to output error stack trace. The problem is, we want to output the stack trace only when verbose > 0. The if statement scattered around the code. So we replaced them with the new utils.print_exception() call. --- shadowsocks/asyncdns.py | 6 ++---- shadowsocks/daemon.py | 10 +++++----- shadowsocks/eventloop.py | 6 +++--- shadowsocks/local.py | 5 +---- shadowsocks/server.py | 5 +---- shadowsocks/tcprelay.py | 12 +++++------- shadowsocks/udprelay.py | 4 ++-- shadowsocks/utils.py | 13 +++++++++++++ 8 files changed, 32 insertions(+), 29 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 73fab9c..9467490 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -25,7 +25,7 @@ import struct import re import logging -from shadowsocks import common, lru_cache, eventloop +from shadowsocks import common, lru_cache, eventloop, utils CACHE_SWEEP_INTERVAL = 30 @@ -221,9 +221,7 @@ def parse_response(data): response.answers.append((an[1], an[2], an[3])) return response except Exception as e: - import traceback - traceback.print_exc() - logging.error(e) + utils.print_exception(e) return None diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index a73e927..c23ec58 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -23,7 +23,7 @@ import sys import logging import signal import time -from shadowsocks import common +from shadowsocks import common, utils # this module is ported from ShadowVPN daemon.c @@ -58,7 +58,7 @@ def write_pid_file(pid_file, pid): fd = os.open(pid_file, os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR) except OSError as e: - logging.error(e) + utils.print_exception(e) return -1 flags = fcntl.fcntl(fd, fcntl.F_GETFD) assert flags != -1 @@ -127,7 +127,7 @@ def daemon_start(pid_file, log_file): freopen(log_file, 'a', sys.stdout) freopen(log_file, 'a', sys.stderr) except IOError as e: - logging.error(e) + utils.print_exception(e) sys.exit(1) @@ -140,7 +140,7 @@ def daemon_stop(pid_file): if not buf: logging.error('not running') except IOError as e: - logging.error(e) + utils.print_exception(e) if e.errno == errno.ENOENT: # always exit 0 if we are sure daemon is not running logging.error('not running') @@ -155,7 +155,7 @@ def daemon_stop(pid_file): logging.error('not running') # always exit 0 if we are sure daemon is not running return - logging.error(e) + utils.print_exception(e) sys.exit(1) else: logging.error('pid is not positive: %d', pid) diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 839842d..77c64ef 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -28,6 +28,8 @@ import errno import logging from collections import defaultdict +from shadowsocks import utils + __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES'] @@ -223,9 +225,7 @@ class EventLoop(object): try: handler(events) except (OSError, IOError) as e: - logging.error(e) - import traceback - traceback.print_exc() + utils.print_exception(e) if self._handlers_to_remove: for handler in self._handlers_to_remove: self._handlers.remove(handler) diff --git a/shadowsocks/local.py b/shadowsocks/local.py index b75fe9b..a4c853e 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -65,10 +65,7 @@ def main(): daemon.set_user(config.get('user', None)) loop.run() except Exception as e: - logging.error(e) - if config['verbose']: - import traceback - traceback.print_exc() + utils.print_exception(e) sys.exit(1) if __name__ == '__main__': diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 34aab2a..27515b5 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -80,10 +80,7 @@ def main(): daemon.set_user(config.get('user', None)) loop.run() except Exception as e: - logging.error(e) - if config['verbose']: - import traceback - traceback.print_exc() + utils.print_exception(e) sys.exit(1) if int(config['workers']) > 1: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 12792f7..a23a58c 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -203,9 +203,7 @@ class TCPRelayHandler(object): errno.EWOULDBLOCK): uncomplete = True else: - logging.error(e) - if self._config['verbose']: - traceback.print_exc() + utils.print_exception(e) self.destroy() return False if uncomplete: @@ -259,7 +257,7 @@ class TCPRelayHandler(object): self._config['fast_open'] = False self.destroy() else: - logging.error(e) + utils.print_exception(e) if self._config['verbose']: traceback.print_exc() self.destroy() @@ -383,7 +381,7 @@ class TCPRelayHandler(object): self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) return except Exception as e: - logging.error(e) + utils.print_exception(e) if self._config['verbose']: traceback.print_exc() self.destroy() @@ -445,7 +443,7 @@ class TCPRelayHandler(object): try: self._write_to_sock(data, self._local_sock) except Exception as e: - logging.error(e) + utils.print_exception(e) if self._config['verbose']: traceback.print_exc() # TODO use logging when debug completed @@ -683,7 +681,7 @@ class TCPRelay(object): errno.EWOULDBLOCK): continue else: - logging.error(e) + utils.print_exception(e) if self._config['verbose']: traceback.print_exc() else: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 0499f0e..80c44bb 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -69,7 +69,7 @@ import struct import errno import random -from shadowsocks import encrypt, eventloop, lru_cache, common +from shadowsocks import encrypt, eventloop, lru_cache, common, utils from shadowsocks.common import parse_header, pack_addr @@ -208,7 +208,7 @@ class UDPRelay(object): if err in (errno.EINPROGRESS, errno.EAGAIN): pass else: - logging.error(e) + utils.print_exception(e) def _handle_client(self, sock): data, r_addr = sock.recvfrom(BUF_SIZE) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 71c6d94..175feef 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -29,6 +29,8 @@ from shadowsocks import encrypt VERBOSE_LEVEL = 5 +verbose = 0 + def check_python(): info = sys.version_info @@ -43,6 +45,14 @@ def check_python(): sys.exit(1) +def print_exception(e): + global verbose + logging.error(e) + if verbose > 0: + import traceback + traceback.print_exc() + + def print_shadowsocks(): version = '' try: @@ -115,6 +125,8 @@ def check_config(config, is_local): def get_config(is_local): + global verbose + logging.basicConfig(level=logging.INFO, format='%(levelname)-s: %(message)s') if is_local: @@ -243,6 +255,7 @@ def get_config(is_local): level = logging.ERROR else: level = logging.INFO + verbose = config['verbose'] logging.basicConfig(level=level, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') From d774286dc0e6d0b2f8b4f20528a69f7b4ac45a18 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 17:26:10 +0800 Subject: [PATCH 045/104] rename utils module into shell Since utils is ambiguous, we want to give the module a more clear role. --- shadowsocks/asyncdns.py | 4 ++-- shadowsocks/daemon.py | 10 +++++----- shadowsocks/eventloop.py | 4 ++-- shadowsocks/local.py | 8 ++++---- shadowsocks/server.py | 8 ++++---- shadowsocks/{utils.py => shell.py} | 0 shadowsocks/tcprelay.py | 16 ++++++++-------- shadowsocks/udprelay.py | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) rename shadowsocks/{utils.py => shell.py} (100%) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 9467490..7e4a4ed 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -25,7 +25,7 @@ import struct import re import logging -from shadowsocks import common, lru_cache, eventloop, utils +from shadowsocks import common, lru_cache, eventloop, shell CACHE_SWEEP_INTERVAL = 30 @@ -221,7 +221,7 @@ def parse_response(data): response.answers.append((an[1], an[2], an[3])) return response except Exception as e: - utils.print_exception(e) + shell.print_exception(e) return None diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index c23ec58..8dc5608 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -23,7 +23,7 @@ import sys import logging import signal import time -from shadowsocks import common, utils +from shadowsocks import common, shell # this module is ported from ShadowVPN daemon.c @@ -58,7 +58,7 @@ def write_pid_file(pid_file, pid): fd = os.open(pid_file, os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR) except OSError as e: - utils.print_exception(e) + shell.print_exception(e) return -1 flags = fcntl.fcntl(fd, fcntl.F_GETFD) assert flags != -1 @@ -127,7 +127,7 @@ def daemon_start(pid_file, log_file): freopen(log_file, 'a', sys.stdout) freopen(log_file, 'a', sys.stderr) except IOError as e: - utils.print_exception(e) + shell.print_exception(e) sys.exit(1) @@ -140,7 +140,7 @@ def daemon_stop(pid_file): if not buf: logging.error('not running') except IOError as e: - utils.print_exception(e) + shell.print_exception(e) if e.errno == errno.ENOENT: # always exit 0 if we are sure daemon is not running logging.error('not running') @@ -155,7 +155,7 @@ def daemon_stop(pid_file): logging.error('not running') # always exit 0 if we are sure daemon is not running return - utils.print_exception(e) + shell.print_exception(e) sys.exit(1) else: logging.error('pid is not positive: %d', pid) diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 77c64ef..42f9205 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -28,7 +28,7 @@ import errno import logging from collections import defaultdict -from shadowsocks import utils +from shadowsocks import shell __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', @@ -225,7 +225,7 @@ class EventLoop(object): try: handler(events) except (OSError, IOError) as e: - utils.print_exception(e) + shell.print_exception(e) if self._handlers_to_remove: for handler in self._handlers_to_remove: self._handlers.remove(handler) diff --git a/shadowsocks/local.py b/shadowsocks/local.py index a4c853e..4255a2e 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -24,11 +24,11 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import utils, daemon, eventloop, tcprelay, udprelay, asyncdns +from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns def main(): - utils.check_python() + shell.check_python() # fix py2exe if hasattr(sys, "frozen") and sys.frozen in \ @@ -36,7 +36,7 @@ def main(): p = os.path.dirname(os.path.abspath(sys.executable)) os.chdir(p) - config = utils.get_config(True) + config = shell.get_config(True) daemon.daemon_exec(config) @@ -65,7 +65,7 @@ def main(): daemon.set_user(config.get('user', None)) loop.run() except Exception as e: - utils.print_exception(e) + shell.print_exception(e) sys.exit(1) if __name__ == '__main__': diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 27515b5..429a20a 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -24,13 +24,13 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import utils, daemon, eventloop, tcprelay, udprelay, asyncdns +from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns def main(): - utils.check_python() + shell.check_python() - config = utils.get_config(False) + config = shell.get_config(False) daemon.daemon_exec(config) @@ -80,7 +80,7 @@ def main(): daemon.set_user(config.get('user', None)) loop.run() except Exception as e: - utils.print_exception(e) + shell.print_exception(e) sys.exit(1) if int(config['workers']) > 1: diff --git a/shadowsocks/utils.py b/shadowsocks/shell.py similarity index 100% rename from shadowsocks/utils.py rename to shadowsocks/shell.py diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index a23a58c..c9ed7bd 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -26,7 +26,7 @@ import logging import traceback import random -from shadowsocks import encrypt, eventloop, utils, common +from shadowsocks import encrypt, eventloop, shell, common from shadowsocks.common import parse_header # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time @@ -203,7 +203,7 @@ class TCPRelayHandler(object): errno.EWOULDBLOCK): uncomplete = True else: - utils.print_exception(e) + shell.print_exception(e) self.destroy() return False if uncomplete: @@ -257,7 +257,7 @@ class TCPRelayHandler(object): self._config['fast_open'] = False self.destroy() else: - utils.print_exception(e) + shell.print_exception(e) if self._config['verbose']: traceback.print_exc() self.destroy() @@ -381,7 +381,7 @@ class TCPRelayHandler(object): self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) return except Exception as e: - utils.print_exception(e) + shell.print_exception(e) if self._config['verbose']: traceback.print_exc() self.destroy() @@ -443,7 +443,7 @@ class TCPRelayHandler(object): try: self._write_to_sock(data, self._local_sock) except Exception as e: - utils.print_exception(e) + shell.print_exception(e) if self._config['verbose']: traceback.print_exc() # TODO use logging when debug completed @@ -630,7 +630,7 @@ class TCPRelay(object): # we just need a sorted last_activity queue and it's faster than heapq # in fact we can do O(1) insertion/remove so we invent our own if self._timeouts: - logging.log(utils.VERBOSE_LEVEL, 'sweeping timeouts') + logging.log(shell.VERBOSE_LEVEL, 'sweeping timeouts') now = time.time() length = len(self._timeouts) pos = self._timeout_offset @@ -663,7 +663,7 @@ class TCPRelay(object): # handle events and dispatch to handlers for sock, fd, event in events: if sock: - logging.log(utils.VERBOSE_LEVEL, 'fd %d %s', fd, + logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd, eventloop.EVENT_NAMES.get(event, event)) if sock == self._server_socket: if event & eventloop.POLL_ERR: @@ -681,7 +681,7 @@ class TCPRelay(object): errno.EWOULDBLOCK): continue else: - utils.print_exception(e) + shell.print_exception(e) if self._config['verbose']: traceback.print_exc() else: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 80c44bb..98bfaaa 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -69,7 +69,7 @@ import struct import errno import random -from shadowsocks import encrypt, eventloop, lru_cache, common, utils +from shadowsocks import encrypt, eventloop, lru_cache, common, shell from shadowsocks.common import parse_header, pack_addr @@ -208,7 +208,7 @@ class UDPRelay(object): if err in (errno.EINPROGRESS, errno.EAGAIN): pass else: - utils.print_exception(e) + shell.print_exception(e) def _handle_client(self, sock): data, r_addr = sock.recvfrom(BUF_SIZE) From 581d6e687f96d05c11f79c50c1abba1be96b9803 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 17:38:01 +0800 Subject: [PATCH 046/104] use localhost in test.py Since now the unit tests is huge, using third party website is not polite. So use localhost instead. --- tests/test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index 4953476..29b57d4 100755 --- a/tests/test.py +++ b/tests/test.py @@ -28,6 +28,8 @@ from subprocess import Popen, PIPE python = ['python'] +default_url = 'http://localhost/' + parser = argparse.ArgumentParser(description='test Shadowsocks') parser.add_argument('-c', '--client-conf', type=str, default=None) parser.add_argument('-s', '--server-conf', type=str, default=None) @@ -36,7 +38,7 @@ parser.add_argument('-b', '--server-args', type=str, default=None) parser.add_argument('--with-coverage', action='store_true', default=None) parser.add_argument('--should-fail', action='store_true', default=None) parser.add_argument('--tcp-only', action='store_true', default=None) -parser.add_argument('--url', type=str, default='http://www.example.com/') +parser.add_argument('--url', type=str, default=default_url) parser.add_argument('--dns', type=str, default='8.8.8.8') config = parser.parse_args() @@ -59,6 +61,8 @@ if config.client_args: server_args.extend(config.server_args.split()) else: server_args.extend(config.client_args.split()) +if config.url == default_url: + server_args.extend(['--forbidden-ip', '']) p1 = Popen(server_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) p2 = Popen(client_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) From 42ce2569c4774cd021ed88dd7b9393d4cb51595d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 17:43:06 +0800 Subject: [PATCH 047/104] also test with real website Now that localhost is used in tests, DNS code is uncovered. Use clients1.google.com/generate_204 to test if a real website works. --- tests/jenkins.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 2cd02a2..71d5b1c 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -49,6 +49,9 @@ run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0 run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" +# test if DNS works +run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://clients1.google.com/generate_204" + # test localhost is in the forbidden list by default run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" From 0e14f3bbefbfb7a37be29e6861864225fe819873 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 18:01:53 +0800 Subject: [PATCH 048/104] fix travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c5caa33..f29cb96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ before_install: - sudo apt-get update -qq - sudo apt-get install -qq build-essential dnsutils iproute nginx bc - sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10 + - sudo sh -c "echo '127.0.0.1 localhost' > /etc/hosts" - sudo service nginx restart - pip install pep8 pyflakes nose coverage - sudo tests/socksify/install.sh From 1b7ab23f78d33730b5f793271bca15985f97a722 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 18:25:36 +0800 Subject: [PATCH 049/104] release 2.6.8 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 8004a3f..2946ecf 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +2.6.8 2015-02-10 +- Support multiple server ip on client side +- Support --version +- Minor fixes + 2.6.7 2015-02-02 - Support --user - Support CIDR format in --forbidden-ip From 294556f8bc972dd8ccf4f6b9e6eba6574725d5c0 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 10 Feb 2015 18:26:06 +0800 Subject: [PATCH 050/104] bump 2.6.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9485ce2..512ff63 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.8", + version="2.6.9", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From edb7822a7b53540833b68810fc73608b212f7125 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Wed, 11 Feb 2015 10:02:35 +0800 Subject: [PATCH 051/104] convert remote_address to str so it will be printed more correctly on python 3 --- shadowsocks/tcprelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index c9ed7bd..4834883 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -295,7 +295,7 @@ class TCPRelayHandler(object): logging.info('connecting %s:%d from %s:%d' % (common.to_str(remote_addr), remote_port, self._client_address[0], self._client_address[1])) - self._remote_address = (remote_addr, remote_port) + self._remote_address = (common.to_str(remote_addr), remote_port) # pause reading self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) self._stage = STAGE_DNS From 4172639d48ceb42c4daec81eb6683ac3dc50b265 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 12 Feb 2015 14:18:18 +0800 Subject: [PATCH 052/104] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aee2bdc..3c04db3 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Bugs and Issues -[Android]: https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#android +[Android]: https://github.com/shadowsocks/shadowsocks-android [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat [Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File [Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks @@ -103,4 +103,4 @@ Bugs and Issues [Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks [Troubleshooting]: https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting [Wiki]: https://github.com/shadowsocks/shadowsocks/wiki -[Windows]: https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#windows +[Windows]: https://github.com/shadowsocks/shadowsocks-csharp From b6e6e14b8a4d864982b4aaf3f4ae6430368cfd0d Mon Sep 17 00:00:00 2001 From: Joshua Lund Date: Sat, 14 Feb 2015 21:33:40 -0700 Subject: [PATCH 053/104] Use AES in the Usage example instead of RC4. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c04db3..76d759a 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,11 @@ See [Install Server on Windows] ### Usage - ssserver -p 443 -k password -m rc4-md5 + ssserver -p 443 -k password -m aes-256-cfb To run in the background: - sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start + sudo ssserver -p 443 -k password -m aes-256-cfb --user nobody -d start To stop: From d3831bef8cf9029a6dc49b1ddbeddc29ec403807 Mon Sep 17 00:00:00 2001 From: lazybios Date: Sun, 1 Mar 2015 14:14:35 +0800 Subject: [PATCH 054/104] remove duplicate code from shell.py --- shadowsocks/shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 175feef..2195c1a 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -157,7 +157,6 @@ def get_config(is_local): else: config = {} - optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) v_count = 0 for key, value in optlist: if key == '-p': From e17279e5bf0b9a04835531bb75b9c54905da1542 Mon Sep 17 00:00:00 2001 From: Kim Wong Date: Sat, 14 Mar 2015 07:35:43 +0800 Subject: [PATCH 055/104] remove duplicated line (refer line 221) --- shadowsocks/shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 2195c1a..f8ae81f 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -221,7 +221,6 @@ def get_config(is_local): config['workers'] = config.get('workers', 1) config['pid-file'] = config.get('pid-file', '/var/run/shadowsocks.pid') config['log-file'] = config.get('log-file', '/var/log/shadowsocks.log') - config['workers'] = config.get('workers', 1) config['verbose'] = config.get('verbose', False) config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) config['local_port'] = config.get('local_port', 1080) From ea7a3e1b585745ec94196eee4bcf94b393920917 Mon Sep 17 00:00:00 2001 From: sky Date: Sun, 3 May 2015 14:34:54 +0800 Subject: [PATCH 056/104] flush autoban output --- utils/autoban.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/autoban.py b/utils/autoban.py index d04ebbe..1bbb65c 100755 --- a/utils/autoban.py +++ b/utils/autoban.py @@ -42,10 +42,12 @@ if __name__ == '__main__': if ip not in ips: ips[ip] = 1 print(ip) + sys.stdout.flush() else: ips[ip] += 1 if ip not in banned and ips[ip] >= config.count: banned.add(ip) cmd = 'iptables -A INPUT -s %s -j DROP' % ip print(cmd, file=sys.stderr) + sys.stderr.flush() os.system(cmd) From 082c8a80f47764ed040ebd2bebebd8711181f1ea Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 19 May 2015 08:13:47 +0800 Subject: [PATCH 057/104] fix duplicated close in LRUCache close #324 --- shadowsocks/lru_cache.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/shadowsocks/lru_cache.py b/shadowsocks/lru_cache.py index 27c0738..b70f743 100644 --- a/shadowsocks/lru_cache.py +++ b/shadowsocks/lru_cache.py @@ -74,6 +74,7 @@ class LRUCache(collections.MutableMapping): # O(m) now = time.time() c = 0 + values_closed = list() # list is cheaper to create while len(self._last_visits) > 0: least = self._last_visits[0] if now - least <= self.timeout: @@ -83,7 +84,9 @@ class LRUCache(collections.MutableMapping): if key in self._store: if now - self._keys_to_last_time[key] > self.timeout: value = self._store[key] - self.close_callback(value) + if value not in values_closed: + self.close_callback(value) + values_closed.append(value) for key in self._time_to_keys[least]: self._last_visits.popleft() if key in self._store: @@ -126,5 +129,21 @@ def test(): assert 'a' not in c assert 'b' not in c + global close_cb_called + close_cb_called = False + + def close_cb(t): + global close_cb_called + assert not close_cb_called + close_cb_called = True + + c = LRUCache(timeout=0.1, close_callback=close_cb) + c['s'] = 1 + c['s'] + time.sleep(0.1) + c['s'] + time.sleep(0.3) + c.sweep() + if __name__ == '__main__': test() From 405120c59fb86f5364c5470dc647c293133d1506 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 19 May 2015 08:19:25 +0800 Subject: [PATCH 058/104] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 512ff63..38def84 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.9", + version="2.6.10", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From c46234af4140715b83c8f148cfcf97b0974881ef Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 19 May 2015 08:20:56 +0800 Subject: [PATCH 059/104] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 2946ecf..602a0a4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.6.9 2015-05-19 +- Fix a stability issue on Windows + 2.6.8 2015-02-10 - Support multiple server ip on client side - Support --version From 16db66675bb1160b4104140a7927036c5552333c Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 19 May 2015 08:35:28 +0800 Subject: [PATCH 060/104] optimize LRUCache --- shadowsocks/lru_cache.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shadowsocks/lru_cache.py b/shadowsocks/lru_cache.py index b70f743..401f19b 100644 --- a/shadowsocks/lru_cache.py +++ b/shadowsocks/lru_cache.py @@ -41,6 +41,7 @@ class LRUCache(collections.MutableMapping): self._time_to_keys = collections.defaultdict(list) self._keys_to_last_time = {} self._last_visits = collections.deque() + self._closed_values = set() self.update(dict(*args, **kwargs)) # use the free update to set keys def __getitem__(self, key): @@ -74,7 +75,6 @@ class LRUCache(collections.MutableMapping): # O(m) now = time.time() c = 0 - values_closed = list() # list is cheaper to create while len(self._last_visits) > 0: least = self._last_visits[0] if now - least <= self.timeout: @@ -84,9 +84,9 @@ class LRUCache(collections.MutableMapping): if key in self._store: if now - self._keys_to_last_time[key] > self.timeout: value = self._store[key] - if value not in values_closed: + if value not in self._closed_values: self.close_callback(value) - values_closed.append(value) + self._closed_values.add(value) for key in self._time_to_keys[least]: self._last_visits.popleft() if key in self._store: @@ -96,6 +96,7 @@ class LRUCache(collections.MutableMapping): c += 1 del self._time_to_keys[least] if c: + self._closed_values.clear() logging.debug('%d keys swept' % c) From e74ae193d06967da9400ab4f4a41958728f887ad Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 8 Jun 2015 08:29:01 +0800 Subject: [PATCH 061/104] elaborate reasons of header parsing failure --- shadowsocks/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 1977dcd..fc03d55 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -171,8 +171,8 @@ def parse_header(data): else: logging.warn('header is too short') else: - logging.warn('unsupported addrtype %d, maybe wrong password' % - addrtype) + logging.warn('unsupported addrtype %d, maybe wrong password or ' + 'encryption method' % addrtype) if dest_addr is None: return None return addrtype, to_bytes(dest_addr), dest_port, header_length From 56c289ba217e64c7397ac80042a6401b1da2af74 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 8 Jun 2015 08:35:30 +0800 Subject: [PATCH 062/104] update CHANGES --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 602a0a4..4db142a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +2.6.10 2015-06-08 +- Optimize LRU cache +- Refine logging + 2.6.9 2015-05-19 - Fix a stability issue on Windows From e001f1818c9dfda2fe581b902207e7d9dcc6bfcf Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 8 Jun 2015 08:36:17 +0800 Subject: [PATCH 063/104] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 38def84..07ea2db 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.10", + version="2.6.11", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 0edae7069f6087e6c25b9809ee0a5f651866f154 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Sun, 21 Jun 2015 00:56:55 +0800 Subject: [PATCH 064/104] Add fail2ban filter. Please put the shadowsocks.conf into your filter.d directory, and using `filter = shadowsocks` to use the filter. --- utils/fail2ban/shadowsocks.conf | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 utils/fail2ban/shadowsocks.conf diff --git a/utils/fail2ban/shadowsocks.conf b/utils/fail2ban/shadowsocks.conf new file mode 100644 index 0000000..9b1c7ec --- /dev/null +++ b/utils/fail2ban/shadowsocks.conf @@ -0,0 +1,5 @@ +[Definition] + +_daemon = shadowsocks + +failregex = ^\s+ERROR\s+can not parse header when handling connection from :\d+$ From 1a62694a3b1d7693ae625e2b6a50a34c84d355eb Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 15:02:33 +0800 Subject: [PATCH 065/104] add udp source port test --- .travis.yml | 2 +- tests/jenkins.sh | 4 +++- tests/test_udp_src.py | 31 +++++++++++++++++++++++++++++++ tests/test_udp_src.sh | 23 +++++++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/test_udp_src.py create mode 100755 tests/test_udp_src.sh diff --git a/.travis.yml b/.travis.yml index f29cb96..535bc9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10 - sudo sh -c "echo '127.0.0.1 localhost' > /etc/hosts" - sudo service nginx restart - - pip install pep8 pyflakes nose coverage + - pip install pep8 pyflakes nose coverage PySocks - sudo tests/socksify/install.sh - sudo tests/libsodium/install.sh - sudo tests/setup_tc.sh diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 71d5b1c..5e4e61f 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -24,6 +24,8 @@ function run_test { return 0 } +pip install PySocks + python --version coverage erase mkdir tmp @@ -69,7 +71,7 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh - +run_test tests/test_udp_src.sh run_test tests/test_command.sh coverage combine && coverage report --include=shadowsocks/* diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py new file mode 100644 index 0000000..83c615d --- /dev/null +++ b/tests/test_udp_src.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +import socket +import socks + +if __name__ == '__main__': + sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.bind(('127.0.0.1', 9000)) + + sock_in1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_in2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + + sock_in1.bind(('127.0.0.1', 9001)) + sock_in2.bind(('127.0.0.1', 9002)) + + sock_out.sendto('data', ('127.0.0.1', 9001)) + result1 = sock_in1.recvfrom(8) + + sock_out.sendto('data', ('127.0.0.1', 9002)) + result2 = sock_in2.recvfrom(8) + + sock_out.close() + sock_in1.close() + sock_in2.close() + + # make sure they're from the same source port + assert result1 == result2 diff --git a/tests/test_udp_src.sh b/tests/test_udp_src.sh new file mode 100755 index 0000000..876f242 --- /dev/null +++ b/tests/test_udp_src.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +PYTHON="coverage run -p -a" + +mkdir -p tmp + +$PYTHON shadowsocks/local.py -c tests/aes.json & +LOCAL=$! + +$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & +SERVER=$! + +sleep 3 + +python tests/test_udp_src.py +r=$? + +kill -s SIGINT $LOCAL +kill -s SIGINT $SERVER + +sleep 2 + +exit $r From c34c99450f7d2410f005e6c81c1955a0c49d7c2e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 16:58:04 +0800 Subject: [PATCH 066/104] fix UDP source port issue --- shadowsocks/udprelay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 98bfaaa..ed6937f 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -76,8 +76,8 @@ from shadowsocks.common import parse_header, pack_addr BUF_SIZE = 65536 -def client_key(a, b, c, d): - return '%s:%s:%s:%s' % (a, b, c, d) +def client_key(source_addr, dest_addr): + return '%s:%s' % (source_addr[0], source_addr[1]) class UDPRelay(object): @@ -169,7 +169,7 @@ class UDPRelay(object): else: server_addr, server_port = dest_addr, dest_port - key = client_key(r_addr[0], r_addr[1], dest_addr, dest_port) + key = client_key(r_addr, (dest_addr, dest_port)) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo From 99b4121fd93f38e89354bca915e3ed620632d6fe Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 17:32:31 +0800 Subject: [PATCH 067/104] fix problem when UDP client requesting both IPv4 and IPv6 --- shadowsocks/udprelay.py | 43 ++++++++++++++++++---------------- tests/jenkins.sh | 2 -- tests/test_udp_src.py | 51 +++++++++++++++++++++++++++++++++++++++-- tests/test_udp_src.sh | 4 ++-- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index ed6937f..888b036 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -76,8 +76,9 @@ from shadowsocks.common import parse_header, pack_addr BUF_SIZE = 65536 -def client_key(source_addr, dest_addr): - return '%s:%s' % (source_addr[0], source_addr[1]) +def client_key(source_addr, server_af): + # notice this is server af, not dest af + return '%s:%s:%d' % (source_addr[0], source_addr[1], server_af) class UDPRelay(object): @@ -169,27 +170,29 @@ class UDPRelay(object): else: server_addr, server_port = dest_addr, dest_port - key = client_key(r_addr, (dest_addr, dest_port)) + addrs = socket.getaddrinfo(server_addr, server_port, 0, + socket.SOCK_DGRAM, socket.SOL_UDP) + if not addrs: + # drop + return + + af, socktype, proto, canonname, sa = addrs[0] + key = client_key(r_addr, af) + logging.debug(key) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo - addrs = socket.getaddrinfo(server_addr, server_port, 0, - socket.SOCK_DGRAM, socket.SOL_UDP) - if addrs: - af, socktype, proto, canonname, sa = addrs[0] - if self._forbidden_iplist: - if common.to_str(sa[0]) in self._forbidden_iplist: - logging.debug('IP %s is in forbidden list, drop' % - common.to_str(sa[0])) - # drop - return - client = socket.socket(af, socktype, proto) - client.setblocking(False) - self._cache[key] = client - self._client_fd_to_server_addr[client.fileno()] = r_addr - else: - # drop - return + if self._forbidden_iplist: + if common.to_str(sa[0]) in self._forbidden_iplist: + logging.debug('IP %s is in forbidden list, drop' % + common.to_str(sa[0])) + # drop + return + client = socket.socket(af, socktype, proto) + client.setblocking(False) + self._cache[key] = client + self._client_fd_to_server_addr[client.fileno()] = r_addr + self._sockets.add(client.fileno()) self._eventloop.add(client, eventloop.POLL_IN) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 5e4e61f..ea5c163 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -24,8 +24,6 @@ function run_test { return 0 } -pip install PySocks - python --version coverage erase mkdir tmp diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index 83c615d..840b8f6 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -4,6 +4,7 @@ import socket import socks if __name__ == '__main__': + # Test 1: same source port IPv4 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) @@ -17,10 +18,10 @@ if __name__ == '__main__': sock_in1.bind(('127.0.0.1', 9001)) sock_in2.bind(('127.0.0.1', 9002)) - sock_out.sendto('data', ('127.0.0.1', 9001)) + sock_out.sendto(b'data', ('127.0.0.1', 9001)) result1 = sock_in1.recvfrom(8) - sock_out.sendto('data', ('127.0.0.1', 9002)) + sock_out.sendto(b'data', ('127.0.0.1', 9002)) result2 = sock_in2.recvfrom(8) sock_out.close() @@ -29,3 +30,49 @@ if __name__ == '__main__': # make sure they're from the same source port assert result1 == result2 + + # Test 2: same source port IPv6 + # try again from the same port but IPv6 + sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.bind(('127.0.0.1', 9000)) + + sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_in2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, + socket.SOL_UDP) + + sock_in1.bind(('::1', 9001)) + sock_in2.bind(('::1', 9002)) + + sock_out.sendto(b'data', ('::1', 9001)) + result1 = sock_in1.recvfrom(8) + + sock_out.sendto(b'data', ('::1', 9002)) + result2 = sock_in2.recvfrom(8) + + sock_out.close() + sock_in1.close() + sock_in2.close() + + # make sure they're from the same source port + assert result1 == result2 + + # Test 3: different source ports IPv6 + sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.bind(('127.0.0.1', 9003)) + + sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_in1.bind(('::1', 9001)) + sock_out.sendto(b'data', ('::1', 9001)) + result3 = sock_in1.recvfrom(8) + + # make sure they're from different source ports + assert result1 != result3 + + sock_out.close() + sock_in1.close() diff --git a/tests/test_udp_src.sh b/tests/test_udp_src.sh index 876f242..d356581 100755 --- a/tests/test_udp_src.sh +++ b/tests/test_udp_src.sh @@ -4,10 +4,10 @@ PYTHON="coverage run -p -a" mkdir -p tmp -$PYTHON shadowsocks/local.py -c tests/aes.json & +$PYTHON shadowsocks/local.py -c tests/aes.json -v & LOCAL=$! -$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & +$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" -v & SERVER=$! sleep 3 From 13a6bb007c14fb1068f2b2c67d26725359cb35e5 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 17:39:24 +0800 Subject: [PATCH 068/104] cache DNS results in UDPRelay --- shadowsocks/udprelay.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 888b036..b90e369 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -103,6 +103,7 @@ class UDPRelay(object): close_callback=self._close_client) self._client_fd_to_server_addr = \ lru_cache.LRUCache(timeout=config['timeout']) + self._dns_cache = lru_cache.LRUCache(timeout=300) self._eventloop = None self._closed = False self._last_time = time.time() @@ -170,11 +171,15 @@ class UDPRelay(object): else: server_addr, server_port = dest_addr, dest_port - addrs = socket.getaddrinfo(server_addr, server_port, 0, - socket.SOCK_DGRAM, socket.SOL_UDP) - if not addrs: - # drop - return + addrs = self._dns_cache.get(server_addr, None) + if addrs is None: + addrs = socket.getaddrinfo(server_addr, server_port, 0, + socket.SOCK_DGRAM, socket.SOL_UDP) + if not addrs: + # drop + return + else: + self._dns_cache[server_addr] = addrs af, socktype, proto, canonname, sa = addrs[0] key = client_key(r_addr, af) From f55bd0302f0f997f6151b8fd1a10f8413625a21c Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 17:59:52 +0800 Subject: [PATCH 069/104] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 4db142a..ada1893 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.6.11 2015-07-10 +- Fix a compatibility issue in UDP Relay + 2.6.10 2015-06-08 - Optimize LRU cache - Refine logging From f7d69db6d15864f0910717f4fd677ae34d936073 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 18:01:34 +0800 Subject: [PATCH 070/104] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 07ea2db..689dd73 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.11", + version="2.6.12", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 1bb0e51e8e2fa31f526caa3fa81c62296dc414a8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 11 Jul 2015 13:05:37 +0800 Subject: [PATCH 071/104] refine tests --- tests/test_udp_src.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index 840b8f6..e8fa505 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -3,11 +3,16 @@ import socket import socks + +SERVER_IP = '127.0.0.1' +SERVER_PORT = 1081 + + if __name__ == '__main__': # Test 1: same source port IPv4 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) sock_out.bind(('127.0.0.1', 9000)) sock_in1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, @@ -35,7 +40,7 @@ if __name__ == '__main__': # try again from the same port but IPv6 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) sock_out.bind(('127.0.0.1', 9000)) sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, @@ -62,7 +67,7 @@ if __name__ == '__main__': # Test 3: different source ports IPv6 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) sock_out.bind(('127.0.0.1', 9003)) sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, From 2555aa8e2bf81e2d2ffd0bbd8a386be6fd1282e9 Mon Sep 17 00:00:00 2001 From: Christopher Meng Date: Mon, 27 Jul 2015 00:38:23 -0400 Subject: [PATCH 072/104] PEP8 indent A tiny change to perfect the indent. --- shadowsocks/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index fc03d55..db4beea 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -157,7 +157,7 @@ def parse_header(data): if len(data) >= 2 + addrlen: dest_addr = data[2:2 + addrlen] dest_port = struct.unpack('>H', data[2 + addrlen:4 + - addrlen])[0] + addrlen])[0] header_length = 4 + addrlen else: logging.warn('header is too short') From 4a8d0774b462841cd8de3b4cf13a365c03fda339 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 18:22:35 +0800 Subject: [PATCH 073/104] optimize performance for multiple ports UDPRelay is broken now --- setup.py | 2 +- shadowsocks/asyncdns.py | 56 +- shadowsocks/eventloop.py | 117 +- shadowsocks/tcprelay.py | 97 +- shadowsocks/udprelay.py | 40 +- tests/gen_multiple_passwd.py | 19 + tests/server-multi-passwd-performance.json | 2008 ++++++++++++++++++++ 7 files changed, 2171 insertions(+), 168 deletions(-) create mode 100644 tests/gen_multiple_passwd.py create mode 100644 tests/server-multi-passwd-performance.json diff --git a/setup.py b/setup.py index 689dd73..195b2bb 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.12", + version="2.7", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 7e4a4ed..e60a383 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -256,7 +256,6 @@ class DNSResolver(object): self._hostname_to_cb = {} self._cb_to_hostname = {} self._cache = lru_cache.LRUCache(timeout=300) - self._last_time = time.time() self._sock = None self._servers = None self._parse_resolv() @@ -304,7 +303,7 @@ class DNSResolver(object): except IOError: self._hosts['localhost'] = '127.0.0.1' - def add_to_loop(self, loop, ref=False): + def add_to_loop(self, loop): if self._loop: raise Exception('already add to loop') self._loop = loop @@ -312,8 +311,8 @@ class DNSResolver(object): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) self._sock.setblocking(False) - loop.add(self._sock, eventloop.POLL_IN) - loop.add_handler(self.handle_events, ref=ref) + loop.add(self._sock, eventloop.POLL_IN, self) + loop.add_periodic(self.handle_periodic) def _call_callback(self, hostname, ip, error=None): callbacks = self._hostname_to_cb.get(hostname, []) @@ -354,30 +353,27 @@ class DNSResolver(object): self._call_callback(hostname, None) break - def handle_events(self, events): - for sock, fd, event in events: - if sock != self._sock: - continue - if event & eventloop.POLL_ERR: - logging.error('dns socket err') - self._loop.remove(self._sock) - self._sock.close() - # TODO when dns server is IPv6 - self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - self._sock.setblocking(False) - self._loop.add(self._sock, eventloop.POLL_IN) - else: - data, addr = sock.recvfrom(1024) - if addr[0] not in self._servers: - logging.warn('received a packet other than our dns') - break - self._handle_data(data) - break - now = time.time() - if now - self._last_time > CACHE_SWEEP_INTERVAL: - self._cache.sweep() - self._last_time = now + def handle_event(self, sock, fd, event): + if sock != self._sock: + return + if event & eventloop.POLL_ERR: + logging.error('dns socket err') + self._loop.remove(self._sock, self) + self._sock.close() + # TODO when dns server is IPv6 + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + self._sock.setblocking(False) + self._loop.add(self._sock, eventloop.POLL_IN, self) + else: + data, addr = sock.recvfrom(1024) + if addr[0] not in self._servers: + logging.warn('received a packet other than our dns') + return + self._handle_data(data) + + def handle_periodic(self): + self._cache.sweep() def remove_callback(self, callback): hostname = self._cb_to_hostname.get(callback) @@ -385,7 +381,7 @@ class DNSResolver(object): del self._cb_to_hostname[callback] arr = self._hostname_to_cb.get(hostname, None) if arr: - arr.remove(callback) + arr.remove(callback, self) if not arr: del self._hostname_to_cb[hostname] if hostname in self._hostname_status: @@ -430,6 +426,7 @@ class DNSResolver(object): def close(self): if self._sock: + self._loop.remove(self._sock, self) self._sock.close() self._sock = None @@ -451,7 +448,6 @@ def test(): print(result, error) counter += 1 if counter == 9: - loop.remove_handler(dns_resolver.handle_events) dns_resolver.close() a_callback = callback return a_callback diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 42f9205..09e9761 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -22,6 +22,7 @@ from __future__ import absolute_import, division, print_function, \ with_statement import os +import time import socket import select import errno @@ -51,23 +52,8 @@ EVENT_NAMES = { POLL_NVAL: 'POLL_NVAL', } - -class EpollLoop(object): - - def __init__(self): - self._epoll = select.epoll() - - def poll(self, timeout): - return self._epoll.poll(timeout) - - def add_fd(self, fd, mode): - self._epoll.register(fd, mode) - - def remove_fd(self, fd): - self._epoll.unregister(fd) - - def modify_fd(self, fd, mode): - self._epoll.modify(fd, mode) +# we check timeouts every TIMEOUT_PRECISION seconds +TIMEOUT_PRECISION = 10 class KqueueLoop(object): @@ -100,17 +86,17 @@ class KqueueLoop(object): results[fd] |= POLL_OUT return results.items() - def add_fd(self, fd, mode): + def register(self, fd, mode): self._fds[fd] = mode self._control(fd, mode, select.KQ_EV_ADD) - def remove_fd(self, fd): + def unregister(self, fd): self._control(fd, self._fds[fd], select.KQ_EV_DELETE) del self._fds[fd] - def modify_fd(self, fd, mode): - self.remove_fd(fd) - self.add_fd(fd, mode) + def modify(self, fd, mode): + self.unregister(fd) + self.register(fd, mode) class SelectLoop(object): @@ -129,7 +115,7 @@ class SelectLoop(object): results[fd] |= p[1] return results.items() - def add_fd(self, fd, mode): + def register(self, fd, mode): if mode & POLL_IN: self._r_list.add(fd) if mode & POLL_OUT: @@ -137,7 +123,7 @@ class SelectLoop(object): if mode & POLL_ERR: self._x_list.add(fd) - def remove_fd(self, fd): + def unregister(self, fd): if fd in self._r_list: self._r_list.remove(fd) if fd in self._w_list: @@ -145,16 +131,15 @@ class SelectLoop(object): if fd in self._x_list: self._x_list.remove(fd) - def modify_fd(self, fd, mode): - self.remove_fd(fd) - self.add_fd(fd, mode) + def modify(self, fd, mode): + self.unregister(fd) + self.register(fd, mode) class EventLoop(object): def __init__(self): - self._iterating = False if hasattr(select, 'epoll'): - self._impl = EpollLoop() + self._impl = select.epoll() model = 'epoll' elif hasattr(select, 'kqueue'): self._impl = KqueueLoop() @@ -166,48 +151,50 @@ class EventLoop(object): raise Exception('can not find any available functions in select ' 'package') self._fd_to_f = {} - self._handlers = [] - self._ref_handlers = [] - self._handlers_to_remove = [] + self._fd_to_handler = {} + self._last_time = time.time() + self._periodic_callbacks = [] + self._stopping = False logging.debug('using event model: %s', model) def poll(self, timeout=None): events = self._impl.poll(timeout) return [(self._fd_to_f[fd], fd, event) for fd, event in events] - def add(self, f, mode): + def add(self, f, mode, handler): fd = f.fileno() self._fd_to_f[fd] = f - self._impl.add_fd(fd, mode) + self._impl.register(fd, mode) + self._fd_to_handler[fd] = handler - def remove(self, f): + def remove(self, f, handler): fd = f.fileno() del self._fd_to_f[fd] - self._impl.remove_fd(fd) + self._impl.unregister(fd) + if handler is not None: + del self._fd_to_handler[fd] - def modify(self, f, mode): + def add_periodic(self, callback): + self._periodic_callbacks.append(callback) + + def remove_periodic(self, callback): + self._periodic_callbacks.remove(callback) + + def modify(self, f, mode, handler): fd = f.fileno() - self._impl.modify_fd(fd, mode) + self._impl.modify(fd, mode) + if handler is not None: + self._fd_to_handler[fd] = handler - def add_handler(self, handler, ref=True): - self._handlers.append(handler) - if ref: - # when all ref handlers are removed, loop stops - self._ref_handlers.append(handler) - - def remove_handler(self, handler): - if handler in self._ref_handlers: - self._ref_handlers.remove(handler) - if self._iterating: - self._handlers_to_remove.append(handler) - else: - self._handlers.remove(handler) + def stop(self): + self._stopping = True def run(self): events = [] - while self._ref_handlers: + while not self._stopping: + now = time.time() try: - events = self.poll(1) + events = self.poll(TIMEOUT_PRECISION) except (OSError, IOError) as e: if errno_from_exception(e) in (errno.EPIPE, errno.EINTR): # EPIPE: Happens when the client closes the connection @@ -219,18 +206,18 @@ class EventLoop(object): import traceback traceback.print_exc() continue - self._iterating = True - for handler in self._handlers: - # TODO when there are a lot of handlers - try: - handler(events) - except (OSError, IOError) as e: - shell.print_exception(e) - if self._handlers_to_remove: - for handler in self._handlers_to_remove: - self._handlers.remove(handler) - self._handlers_to_remove = [] - self._iterating = False + + for sock, fd, event in events: + handler = self._fd_to_handler.get(fd, None) + if handler is not None: + try: + handler.handle_event(sock, fd, event) + except (OSError, IOError) as e: + shell.print_exception(e) + if now - self._last_time >= TIMEOUT_PRECISION: + for callback in self._periodic_callbacks: + callback() + self._last_time = now # from tornado diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 4834883..3300df3 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -32,9 +32,6 @@ from shadowsocks.common import parse_header # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time TIMEOUTS_CLEAN_SIZE = 512 -# we check timeouts every TIMEOUT_PRECISION seconds -TIMEOUT_PRECISION = 4 - MSG_FASTOPEN = 0x20000000 # SOCKS command definition @@ -126,7 +123,8 @@ class TCPRelayHandler(object): fd_to_handlers[local_sock.fileno()] = self local_sock.setblocking(False) local_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) - loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR) + loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR, + self._server) self.last_activity = 0 self._update_activity() @@ -175,14 +173,14 @@ class TCPRelayHandler(object): event |= eventloop.POLL_OUT if self._upstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN - self._loop.modify(self._local_sock, event) + self._loop.modify(self._local_sock, event, self._server) if self._remote_sock: event = eventloop.POLL_ERR if self._downstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN if self._upstream_status & WAIT_STATUS_WRITING: event |= eventloop.POLL_OUT - self._loop.modify(self._remote_sock, event) + self._loop.modify(self._remote_sock, event, self._server) def _write_to_sock(self, data, sock): # write data to sock @@ -238,7 +236,7 @@ class TCPRelayHandler(object): remote_sock = \ self._create_remote_socket(self._chosen_server[0], self._chosen_server[1]) - self._loop.add(remote_sock, eventloop.POLL_ERR) + self._loop.add(remote_sock, eventloop.POLL_ERR, self._server) data = b''.join(self._data_to_write_to_remote) l = len(data) s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server) @@ -375,7 +373,8 @@ class TCPRelayHandler(object): errno.EINPROGRESS: pass self._loop.add(remote_sock, - eventloop.POLL_ERR | eventloop.POLL_OUT) + eventloop.POLL_ERR | eventloop.POLL_OUT, + self._server) self._stage = STAGE_CONNECTING self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) @@ -535,13 +534,13 @@ class TCPRelayHandler(object): logging.debug('destroy') if self._remote_sock: logging.debug('destroying remote') - self._loop.remove(self._remote_sock) + self._loop.remove(self._remote_sock, self._server) del self._fd_to_handlers[self._remote_sock.fileno()] self._remote_sock.close() self._remote_sock = None if self._local_sock: logging.debug('destroying local') - self._loop.remove(self._local_sock) + self._loop.remove(self._local_sock, self._server) del self._fd_to_handlers[self._local_sock.fileno()] self._local_sock.close() self._local_sock = None @@ -557,7 +556,6 @@ class TCPRelay(object): self._closed = False self._eventloop = None self._fd_to_handlers = {} - self._last_time = time.time() self._timeout = config['timeout'] self._timeouts = [] # a list for all the handlers @@ -598,10 +596,9 @@ class TCPRelay(object): if self._closed: raise Exception('already closed') self._eventloop = loop - loop.add_handler(self._handle_events) - self._eventloop.add(self._server_socket, - eventloop.POLL_IN | eventloop.POLL_ERR) + eventloop.POLL_IN | eventloop.POLL_ERR, self) + self._eventloop.add_periodic(self.handle_periodic) def remove_handler(self, handler): index = self._handler_to_timeouts.get(hash(handler), -1) @@ -613,7 +610,7 @@ class TCPRelay(object): def update_activity(self, handler): # set handler to active now = int(time.time()) - if now - handler.last_activity < TIMEOUT_PRECISION: + if now - handler.last_activity < eventloop.TIMEOUT_PRECISION: # thus we can lower timeout modification frequency return handler.last_activity = now @@ -659,51 +656,49 @@ class TCPRelay(object): pos = 0 self._timeout_offset = pos - def _handle_events(self, events): + def handle_event(self, sock, fd, event): # handle events and dispatch to handlers - for sock, fd, event in events: - if sock: - logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd, - eventloop.EVENT_NAMES.get(event, event)) - if sock == self._server_socket: - if event & eventloop.POLL_ERR: - # TODO - raise Exception('server_socket error') - try: - logging.debug('accept') - conn = self._server_socket.accept() - TCPRelayHandler(self, self._fd_to_handlers, - self._eventloop, conn[0], self._config, - self._dns_resolver, self._is_local) - except (OSError, IOError) as e: - error_no = eventloop.errno_from_exception(e) - if error_no in (errno.EAGAIN, errno.EINPROGRESS, - errno.EWOULDBLOCK): - continue - else: - shell.print_exception(e) - if self._config['verbose']: - traceback.print_exc() - else: - if sock: - handler = self._fd_to_handlers.get(fd, None) - if handler: - handler.handle_event(sock, event) + if sock: + logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd, + eventloop.EVENT_NAMES.get(event, event)) + if sock == self._server_socket: + if event & eventloop.POLL_ERR: + # TODO + raise Exception('server_socket error') + try: + logging.debug('accept') + conn = self._server_socket.accept() + TCPRelayHandler(self, self._fd_to_handlers, + self._eventloop, conn[0], self._config, + self._dns_resolver, self._is_local) + except (OSError, IOError) as e: + error_no = eventloop.errno_from_exception(e) + if error_no in (errno.EAGAIN, errno.EINPROGRESS, + errno.EWOULDBLOCK): + return else: - logging.warn('poll removed fd') + shell.print_exception(e) + if self._config['verbose']: + traceback.print_exc() + else: + if sock: + handler = self._fd_to_handlers.get(fd, None) + if handler: + handler.handle_event(sock, event) + else: + logging.warn('poll removed fd') - now = time.time() - if now - self._last_time > TIMEOUT_PRECISION: - self._sweep_timeout() - self._last_time = now + def handle_periodic(self): + self._sweep_timeout() if self._closed: if self._server_socket: - self._eventloop.remove(self._server_socket) + self._eventloop.remove(self._server_socket, self) + self._eventloop.remove_periodic(self.handle_periodic) self._server_socket.close() self._server_socket = None logging.info('closed listen port %d', self._listen_port) if not self._fd_to_handlers: - self._eventloop.remove_handler(self._handle_events) + self._eventloop.stop() def close(self, next_tick=False): self._closed = True diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index b90e369..7e5e2c3 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -106,7 +106,6 @@ class UDPRelay(object): self._dns_cache = lru_cache.LRUCache(timeout=300) self._eventloop = None self._closed = False - self._last_time = time.time() self._sockets = set() if 'forbidden_ip' in config: self._forbidden_iplist = config['forbidden_ip'] @@ -137,7 +136,7 @@ class UDPRelay(object): def _close_client(self, client): if hasattr(client, 'close'): self._sockets.remove(client.fileno()) - self._eventloop.remove(client) + self._eventloop.remove(client, self) client.close() else: # just an address @@ -199,7 +198,7 @@ class UDPRelay(object): self._client_fd_to_server_addr[client.fileno()] = r_addr self._sockets.add(client.fileno()) - self._eventloop.add(client, eventloop.POLL_IN) + self._eventloop.add(client, eventloop.POLL_IN, self) if self._is_local: data = encrypt.encrypt_all(self._password, self._method, 1, data) @@ -257,34 +256,33 @@ class UDPRelay(object): if self._closed: raise Exception('already closed') self._eventloop = loop - loop.add_handler(self._handle_events) server_socket = self._server_socket self._eventloop.add(server_socket, - eventloop.POLL_IN | eventloop.POLL_ERR) + eventloop.POLL_IN | eventloop.POLL_ERR, self) + loop.add_periodic(self.handle_periodic) - def _handle_events(self, events): - for sock, fd, event in events: - if sock == self._server_socket: - if event & eventloop.POLL_ERR: - logging.error('UDP server_socket err') - self._handle_server() - elif sock and (fd in self._sockets): - if event & eventloop.POLL_ERR: - logging.error('UDP client_socket err') - self._handle_client(sock) - now = time.time() - if now - self._last_time > 3: - self._cache.sweep() - self._client_fd_to_server_addr.sweep() - self._last_time = now + def handle_event(self, sock, fd, event): + if sock == self._server_socket: + if event & eventloop.POLL_ERR: + logging.error('UDP server_socket err') + self._handle_server() + elif sock and (fd in self._sockets): + if event & eventloop.POLL_ERR: + logging.error('UDP client_socket err') + self._handle_client(sock) + + def handle_periodic(self): + self._cache.sweep() + self._client_fd_to_server_addr.sweep() if self._closed: self._server_socket.close() for sock in self._sockets: sock.close() - self._eventloop.remove_handler(self._handle_events) + self._eventloop.remove_periodic(self.handle_periodic) def close(self, next_tick=False): self._closed = True if not next_tick: + self._eventloop.remove(self._server_socket, self) self._server_socket.close() diff --git a/tests/gen_multiple_passwd.py b/tests/gen_multiple_passwd.py new file mode 100644 index 0000000..a058582 --- /dev/null +++ b/tests/gen_multiple_passwd.py @@ -0,0 +1,19 @@ +#!/usr/bin/python + +import os +import json + +with open('server-multi-passwd-performance.json', 'wb') as f: + r = { + 'server': '127.0.0.1', + 'local_port': 1081, + 'timeout': 60, + 'method': 'aes-256-cfb' + } + ports = {} + for i in range(7000, 9000): + ports[str(i)] = 'aes_password' + + r['port_password'] = ports + print(r) + f.write(json.dumps(r, indent=4).encode('utf-8')) diff --git a/tests/server-multi-passwd-performance.json b/tests/server-multi-passwd-performance.json new file mode 100644 index 0000000..c9fbc37 --- /dev/null +++ b/tests/server-multi-passwd-performance.json @@ -0,0 +1,2008 @@ +{ + "server": "127.0.0.1", + "local_port": 1081, + "port_password": { + "7582": "aes_password", + "7672": "aes_password", + "8923": "aes_password", + "8502": "aes_password", + "8282": "aes_password", + "8871": "aes_password", + "7732": "aes_password", + "8671": "aes_password", + "7018": "aes_password", + "8492": "aes_password", + "7748": "aes_password", + "8992": "aes_password", + "8246": "aes_password", + "7127": "aes_password", + "7775": "aes_password", + "8542": "aes_password", + "8488": "aes_password", + "7515": "aes_password", + "7659": "aes_password", + "8892": "aes_password", + "8028": "aes_password", + "7276": "aes_password", + "7959": "aes_password", + "7457": "aes_password", + "8635": "aes_password", + "7592": "aes_password", + "8764": "aes_password", + "8861": "aes_password", + "8842": "aes_password", + "8135": "aes_password", + "8140": "aes_password", + "8376": "aes_password", + "7733": "aes_password", + "8174": "aes_password", + "7265": "aes_password", + "8314": "aes_password", + "8772": "aes_password", + "8991": "aes_password", + "7183": "aes_password", + "7067": "aes_password", + "7730": "aes_password", + "8694": "aes_password", + "7629": "aes_password", + "7041": "aes_password", + "8507": "aes_password", + "8112": "aes_password", + "8491": "aes_password", + "7273": "aes_password", + "8811": "aes_password", + "8947": "aes_password", + "8612": "aes_password", + "8134": "aes_password", + "8422": "aes_password", + "8970": "aes_password", + "7051": "aes_password", + "8158": "aes_password", + "8934": "aes_password", + "7579": "aes_password", + "7140": "aes_password", + "8448": "aes_password", + "8536": "aes_password", + "7554": "aes_password", + "8168": "aes_password", + "8307": "aes_password", + "8946": "aes_password", + "7872": "aes_password", + "7330": "aes_password", + "8208": "aes_password", + "7955": "aes_password", + "8597": "aes_password", + "7025": "aes_password", + "7086": "aes_password", + "7534": "aes_password", + "7311": "aes_password", + "7758": "aes_password", + "7103": "aes_password", + "8408": "aes_password", + "7688": "aes_password", + "7073": "aes_password", + "8963": "aes_password", + "8578": "aes_password", + "7735": "aes_password", + "7657": "aes_password", + "7763": "aes_password", + "7680": "aes_password", + "8627": "aes_password", + "8205": "aes_password", + "7188": "aes_password", + "8743": "aes_password", + "8472": "aes_password", + "8823": "aes_password", + "7167": "aes_password", + "7008": "aes_password", + "7601": "aes_password", + "8603": "aes_password", + "8467": "aes_password", + "8803": "aes_password", + "7014": "aes_password", + "7233": "aes_password", + "7199": "aes_password", + "7192": "aes_password", + "7329": "aes_password", + "8031": "aes_password", + "8584": "aes_password", + "8041": "aes_password", + "8962": "aes_password", + "8824": "aes_password", + "8079": "aes_password", + "8049": "aes_password", + "7743": "aes_password", + "8035": "aes_password", + "8212": "aes_password", + "8452": "aes_password", + "8484": "aes_password", + "8232": "aes_password", + "8444": "aes_password", + "8410": "aes_password", + "7110": "aes_password", + "7505": "aes_password", + "8856": "aes_password", + "8293": "aes_password", + "7967": "aes_password", + "8267": "aes_password", + "7772": "aes_password", + "8864": "aes_password", + "8518": "aes_password", + "7520": "aes_password", + "7976": "aes_password", + "8407": "aes_password", + "8971": "aes_password", + "7389": "aes_password", + "7510": "aes_password", + "7373": "aes_password", + "8013": "aes_password", + "8310": "aes_password", + "7028": "aes_password", + "7874": "aes_password", + "7356": "aes_password", + "7729": "aes_password", + "7427": "aes_password", + "8312": "aes_password", + "7721": "aes_password", + "7020": "aes_password", + "8231": "aes_password", + "8188": "aes_password", + "8869": "aes_password", + "8595": "aes_password", + "8022": "aes_password", + "8911": "aes_password", + "7957": "aes_password", + "7141": "aes_password", + "7157": "aes_password", + "8471": "aes_password", + "8157": "aes_password", + "8795": "aes_password", + "7087": "aes_password", + "7470": "aes_password", + "7266": "aes_password", + "8072": "aes_password", + "8346": "aes_password", + "7163": "aes_password", + "8954": "aes_password", + "7046": "aes_password", + "7856": "aes_password", + "7883": "aes_password", + "8198": "aes_password", + "8443": "aes_password", + "8496": "aes_password", + "8900": "aes_password", + "8354": "aes_password", + "8758": "aes_password", + "8287": "aes_password", + "7574": "aes_password", + "8316": "aes_password", + "7539": "aes_password", + "8460": "aes_password", + "7616": "aes_password", + "8599": "aes_password", + "7795": "aes_password", + "7079": "aes_password", + "8468": "aes_password", + "8462": "aes_password", + "8645": "aes_password", + "8347": "aes_password", + "8776": "aes_password", + "7072": "aes_password", + "8781": "aes_password", + "7765": "aes_password", + "8048": "aes_password", + "7401": "aes_password", + "8718": "aes_password", + "8712": "aes_password", + "7801": "aes_password", + "8673": "aes_password", + "8791": "aes_password", + "7567": "aes_password", + "7003": "aes_password", + "7358": "aes_password", + "8916": "aes_password", + "7021": "aes_password", + "7487": "aes_password", + "7499": "aes_password", + "7108": "aes_password", + "7501": "aes_password", + "7313": "aes_password", + "8887": "aes_password", + "8724": "aes_password", + "7376": "aes_password", + "7153": "aes_password", + "7377": "aes_password", + "8426": "aes_password", + "8831": "aes_password", + "7380": "aes_password", + "7958": "aes_password", + "8250": "aes_password", + "8155": "aes_password", + "8435": "aes_password", + "7630": "aes_password", + "8026": "aes_password", + "7533": "aes_password", + "8704": "aes_password", + "8411": "aes_password", + "7645": "aes_password", + "7937": "aes_password", + "7488": "aes_password", + "8750": "aes_password", + "7196": "aes_password", + "8714": "aes_password", + "8677": "aes_password", + "7475": "aes_password", + "7625": "aes_password", + "8234": "aes_password", + "8870": "aes_password", + "7147": "aes_password", + "8417": "aes_password", + "7362": "aes_password", + "7341": "aes_password", + "8896": "aes_password", + "8423": "aes_password", + "8884": "aes_password", + "7220": "aes_password", + "8615": "aes_password", + "8719": "aes_password", + "8575": "aes_password", + "8891": "aes_password", + "8210": "aes_password", + "8289": "aes_password", + "7406": "aes_password", + "7692": "aes_password", + "7518": "aes_password", + "7244": "aes_password", + "8561": "aes_password", + "7325": "aes_password", + "7306": "aes_password", + "8266": "aes_password", + "8136": "aes_password", + "7991": "aes_password", + "8844": "aes_password", + "8259": "aes_password", + "7749": "aes_password", + "7238": "aes_password", + "7952": "aes_password", + "8528": "aes_password", + "8477": "aes_password", + "7555": "aes_password", + "7544": "aes_password", + "7478": "aes_password", + "7112": "aes_password", + "8931": "aes_password", + "8082": "aes_password", + "8189": "aes_password", + "8461": "aes_password", + "7740": "aes_password", + "8633": "aes_password", + "8322": "aes_password", + "7093": "aes_password", + "8415": "aes_password", + "8093": "aes_password", + "8682": "aes_password", + "7860": "aes_password", + "8580": "aes_password", + "8503": "aes_password", + "7794": "aes_password", + "8394": "aes_password", + "8487": "aes_password", + "8053": "aes_password", + "7277": "aes_password", + "7241": "aes_password", + "8430": "aes_password", + "8428": "aes_password", + "7805": "aes_password", + "8393": "aes_password", + "7711": "aes_password", + "7807": "aes_password", + "7600": "aes_password", + "8403": "aes_password", + "8141": "aes_password", + "8937": "aes_password", + "7559": "aes_password", + "7834": "aes_password", + "8837": "aes_password", + "7823": "aes_password", + "8928": "aes_password", + "7083": "aes_password", + "7590": "aes_password", + "8806": "aes_password", + "7130": "aes_password", + "7929": "aes_password", + "8684": "aes_password", + "8195": "aes_password", + "8706": "aes_password", + "7044": "aes_password", + "7403": "aes_password", + "8890": "aes_password", + "8364": "aes_password", + "8206": "aes_password", + "7882": "aes_password", + "8522": "aes_password", + "8958": "aes_password", + "7429": "aes_password", + "8586": "aes_password", + "8330": "aes_password", + "8922": "aes_password", + "7940": "aes_password", + "7379": "aes_password", + "8955": "aes_password", + "7168": "aes_password", + "7294": "aes_password", + "7949": "aes_password", + "7384": "aes_password", + "8832": "aes_password", + "7423": "aes_password", + "8763": "aes_password", + "7148": "aes_password", + "7029": "aes_password", + "7969": "aes_password", + "8190": "aes_password", + "8807": "aes_password", + "8889": "aes_password", + "7750": "aes_password", + "7348": "aes_password", + "7193": "aes_password", + "7459": "aes_password", + "7507": "aes_password", + "7536": "aes_password", + "8734": "aes_password", + "7174": "aes_password", + "8400": "aes_password", + "8630": "aes_password", + "7128": "aes_password", + "7261": "aes_password", + "7527": "aes_password", + "7232": "aes_password", + "7843": "aes_password", + "7326": "aes_password", + "8639": "aes_password", + "7830": "aes_password", + "7981": "aes_password", + "8404": "aes_password", + "8888": "aes_password", + "7920": "aes_password", + "7410": "aes_password", + "7204": "aes_password", + "8382": "aes_password", + "8355": "aes_password", + "7700": "aes_password", + "7606": "aes_password", + "7372": "aes_password", + "8106": "aes_password", + "8160": "aes_password", + "7511": "aes_password", + "8204": "aes_password", + "8732": "aes_password", + "8751": "aes_password", + "7727": "aes_password", + "7137": "aes_password", + "8311": "aes_password", + "8587": "aes_password", + "7336": "aes_password", + "7674": "aes_password", + "8009": "aes_password", + "7230": "aes_password", + "7383": "aes_password", + "8867": "aes_password", + "7260": "aes_password", + "7497": "aes_password", + "7390": "aes_password", + "8821": "aes_password", + "7274": "aes_password", + "7285": "aes_password", + "7857": "aes_password", + "8137": "aes_password", + "7114": "aes_password", + "7979": "aes_password", + "8726": "aes_password", + "7227": "aes_password", + "7714": "aes_password", + "8012": "aes_password", + "7613": "aes_password", + "8876": "aes_password", + "7622": "aes_password", + "8582": "aes_password", + "7120": "aes_password", + "7104": "aes_password", + "8785": "aes_password", + "8096": "aes_password", + "8129": "aes_password", + "8481": "aes_password", + "8695": "aes_password", + "7473": "aes_password", + "8163": "aes_password", + "8357": "aes_password", + "8501": "aes_password", + "7177": "aes_password", + "7931": "aes_password", + "8220": "aes_password", + "7399": "aes_password", + "7956": "aes_password", + "8801": "aes_password", + "7719": "aes_password", + "8042": "aes_password", + "7433": "aes_password", + "7827": "aes_password", + "8377": "aes_password", + "7745": "aes_password", + "7302": "aes_password", + "8399": "aes_password", + "7766": "aes_password", + "8720": "aes_password", + "8685": "aes_password", + "8558": "aes_password", + "7796": "aes_password", + "7319": "aes_password", + "7170": "aes_password", + "7342": "aes_password", + "7191": "aes_password", + "8747": "aes_password", + "7231": "aes_password", + "7817": "aes_password", + "7352": "aes_password", + "7057": "aes_password", + "8177": "aes_password", + "7221": "aes_password", + "7297": "aes_password", + "7686": "aes_password", + "7082": "aes_password", + "8414": "aes_password", + "8529": "aes_password", + "7257": "aes_password", + "7300": "aes_password", + "7159": "aes_password", + "8901": "aes_password", + "7578": "aes_password", + "8479": "aes_password", + "7225": "aes_password", + "8286": "aes_password", + "7182": "aes_password", + "8194": "aes_password", + "8850": "aes_password", + "7847": "aes_password", + "7665": "aes_password", + "8011": "aes_password", + "7702": "aes_password", + "8638": "aes_password", + "7116": "aes_password", + "7301": "aes_password", + "8936": "aes_password", + "8661": "aes_password", + "8333": "aes_password", + "8025": "aes_password", + "7368": "aes_password", + "8634": "aes_password", + "7154": "aes_password", + "8365": "aes_password", + "8736": "aes_password", + "8478": "aes_password", + "7436": "aes_password", + "7411": "aes_password", + "7913": "aes_password", + "8236": "aes_password", + "8854": "aes_password", + "8722": "aes_password", + "8227": "aes_password", + "7757": "aes_password", + "8835": "aes_password", + "8651": "aes_password", + "7417": "aes_password", + "7877": "aes_password", + "7200": "aes_password", + "8622": "aes_password", + "7004": "aes_password", + "8845": "aes_password", + "8159": "aes_password", + "8741": "aes_password", + "7106": "aes_password", + "8897": "aes_password", + "7968": "aes_password", + "7047": "aes_password", + "8860": "aes_password", + "8777": "aes_password", + "7597": "aes_password", + "8859": "aes_password", + "7117": "aes_password", + "8178": "aes_password", + "8642": "aes_password", + "7246": "aes_password", + "7557": "aes_password", + "7965": "aes_password", + "7699": "aes_password", + "8658": "aes_password", + "7442": "aes_password", + "8272": "aes_password", + "7821": "aes_password", + "7893": "aes_password", + "8665": "aes_password", + "8499": "aes_password", + "7897": "aes_password", + "7173": "aes_password", + "7007": "aes_password", + "8219": "aes_password", + "8040": "aes_password", + "7571": "aes_password", + "7526": "aes_password", + "8203": "aes_password", + "7810": "aes_password", + "8974": "aes_password", + "8200": "aes_password", + "7778": "aes_password", + "7987": "aes_password", + "7701": "aes_password", + "7443": "aes_password", + "7798": "aes_password", + "8995": "aes_password", + "8473": "aes_password", + "7132": "aes_password", + "7262": "aes_password", + "7720": "aes_password", + "7282": "aes_password", + "8066": "aes_password", + "7006": "aes_password", + "7197": "aes_password", + "7815": "aes_password", + "7933": "aes_password", + "8138": "aes_password", + "8418": "aes_password", + "7365": "aes_password", + "7786": "aes_password", + "7891": "aes_password", + "8317": "aes_password", + "8207": "aes_password", + "8416": "aes_password", + "7448": "aes_password", + "8843": "aes_password", + "7371": "aes_password", + "8780": "aes_password", + "7989": "aes_password", + "8043": "aes_password", + "7363": "aes_password", + "7550": "aes_password", + "8678": "aes_password", + "7837": "aes_password", + "8302": "aes_password", + "7907": "aes_password", + "8865": "aes_password", + "8153": "aes_password", + "8090": "aes_password", + "7268": "aes_password", + "8292": "aes_password", + "7919": "aes_password", + "8131": "aes_password", + "8815": "aes_password", + "8154": "aes_password", + "7777": "aes_password", + "8369": "aes_password", + "8929": "aes_password", + "7670": "aes_password", + "7484": "aes_password", + "8353": "aes_password", + "8017": "aes_password", + "8833": "aes_password", + "8001": "aes_password", + "8058": "aes_password", + "7918": "aes_password", + "7694": "aes_password", + "7485": "aes_password", + "8592": "aes_password", + "7584": "aes_password", + "8527": "aes_password", + "8285": "aes_password", + "8264": "aes_password", + "8879": "aes_password", + "7944": "aes_password", + "7000": "aes_password", + "7187": "aes_password", + "7039": "aes_password", + "7769": "aes_password", + "8063": "aes_password", + "8701": "aes_password", + "7432": "aes_password", + "8594": "aes_password", + "7052": "aes_password", + "7369": "aes_password", + "8474": "aes_password", + "7709": "aes_password", + "8296": "aes_password", + "8278": "aes_password", + "8395": "aes_password", + "8674": "aes_password", + "8733": "aes_password", + "7890": "aes_password", + "7080": "aes_password", + "7528": "aes_password", + "7782": "aes_password", + "7466": "aes_password", + "7903": "aes_password", + "8105": "aes_password", + "7144": "aes_password", + "8069": "aes_password", + "8254": "aes_password", + "8573": "aes_password", + "7787": "aes_password", + "7577": "aes_password", + "8918": "aes_password", + "8767": "aes_password", + "7611": "aes_password", + "8233": "aes_password", + "7802": "aes_password", + "7129": "aes_password", + "8968": "aes_password", + "8217": "aes_password", + "8176": "aes_password", + "8027": "aes_password", + "7941": "aes_password", + "8070": "aes_password", + "8697": "aes_password", + "8798": "aes_password", + "8553": "aes_password", + "8510": "aes_password", + "7254": "aes_password", + "7386": "aes_password", + "8800": "aes_password", + "7712": "aes_password", + "7844": "aes_password", + "7535": "aes_password", + "8273": "aes_password", + "8875": "aes_password", + "8675": "aes_password", + "7396": "aes_password", + "7649": "aes_password", + "8623": "aes_password", + "7186": "aes_password", + "8125": "aes_password", + "7734": "aes_password", + "8275": "aes_password", + "7521": "aes_password", + "7135": "aes_password", + "8546": "aes_password", + "7867": "aes_password", + "8989": "aes_password", + "8787": "aes_password", + "8225": "aes_password", + "8457": "aes_password", + "7405": "aes_password", + "7588": "aes_password", + "7854": "aes_password", + "7789": "aes_password", + "8133": "aes_password", + "7641": "aes_password", + "8535": "aes_password", + "7849": "aes_password", + "7594": "aes_password", + "8313": "aes_password", + "8097": "aes_password", + "8030": "aes_password", + "8737": "aes_password", + "8260": "aes_password", + "8950": "aes_password", + "7249": "aes_password", + "7644": "aes_password", + "7912": "aes_password", + "8979": "aes_password", + "8998": "aes_password", + "8731": "aes_password", + "8662": "aes_password", + "7983": "aes_password", + "7035": "aes_password", + "7841": "aes_password", + "8600": "aes_password", + "7228": "aes_password", + "7071": "aes_password", + "8080": "aes_password", + "8713": "aes_password", + "7210": "aes_password", + "7935": "aes_password", + "8057": "aes_password", + "8242": "aes_password", + "7084": "aes_password", + "7070": "aes_password", + "7494": "aes_password", + "8451": "aes_password", + "8626": "aes_password", + "8618": "aes_password", + "7741": "aes_password", + "7118": "aes_password", + "7387": "aes_password", + "7715": "aes_password", + "8143": "aes_password", + "7668": "aes_password", + "7716": "aes_password", + "8101": "aes_password", + "7234": "aes_password", + "8021": "aes_password", + "7156": "aes_password", + "8392": "aes_password", + "7900": "aes_password", + "7055": "aes_password", + "8566": "aes_password", + "7869": "aes_password", + "7864": "aes_password", + "8265": "aes_password", + "8216": "aes_password", + "7738": "aes_password", + "7467": "aes_password", + "8447": "aes_password", + "8564": "aes_password", + "7767": "aes_password", + "7811": "aes_password", + "7898": "aes_password", + "8182": "aes_password", + "8065": "aes_password", + "7561": "aes_password", + "8545": "aes_password", + "7253": "aes_password", + "8173": "aes_password", + "7922": "aes_password", + "7951": "aes_password", + "7612": "aes_password", + "7324": "aes_password", + "7549": "aes_password", + "7858": "aes_password", + "8655": "aes_password", + "8211": "aes_password", + "8469": "aes_password", + "7298": "aes_password", + "8380": "aes_password", + "7394": "aes_password", + "7089": "aes_password", + "8060": "aes_password", + "7591": "aes_password", + "7542": "aes_password", + "7540": "aes_password", + "7456": "aes_password", + "7768": "aes_password", + "8489": "aes_password", + "8089": "aes_password", + "7838": "aes_password", + "8644": "aes_password", + "8344": "aes_password", + "7739": "aes_password", + "7984": "aes_password", + "7909": "aes_password", + "8517": "aes_password", + "8056": "aes_password", + "8297": "aes_password", + "8647": "aes_password", + "8334": "aes_password", + "7056": "aes_password", + "7164": "aes_password", + "8878": "aes_password", + "7816": "aes_password", + "7444": "aes_password", + "8996": "aes_password", + "8359": "aes_password", + "7901": "aes_password", + "8127": "aes_password", + "7424": "aes_password", + "8116": "aes_password", + "7354": "aes_password", + "8919": "aes_password", + "7214": "aes_password", + "7589": "aes_password", + "8982": "aes_password", + "8244": "aes_password", + "8295": "aes_password", + "7669": "aes_password", + "7562": "aes_password", + "8470": "aes_password", + "7474": "aes_password", + "7345": "aes_password", + "7799": "aes_password", + "8269": "aes_password", + "8213": "aes_password", + "8786": "aes_password", + "7482": "aes_password", + "8261": "aes_password", + "8755": "aes_password", + "8882": "aes_password", + "7166": "aes_password", + "7428": "aes_password", + "8766": "aes_password", + "7458": "aes_password", + "8372": "aes_password", + "8045": "aes_password", + "8185": "aes_password", + "7602": "aes_password", + "8373": "aes_password", + "7826": "aes_password", + "8249": "aes_password", + "8881": "aes_password", + "8830": "aes_password", + "8044": "aes_password", + "7563": "aes_password", + "7509": "aes_password", + "7290": "aes_password", + "7019": "aes_password", + "8454": "aes_password", + "8637": "aes_password", + "7876": "aes_password", + "8500": "aes_password", + "8226": "aes_password", + "7744": "aes_password", + "7753": "aes_password", + "7017": "aes_password", + "8362": "aes_password", + "7642": "aes_password", + "8091": "aes_password", + "7512": "aes_password", + "7708": "aes_password", + "8335": "aes_password", + "7292": "aes_password", + "8281": "aes_password", + "8132": "aes_password", + "7683": "aes_password", + "7048": "aes_password", + "7316": "aes_password", + "7011": "aes_password", + "8299": "aes_password", + "8440": "aes_password", + "8147": "aes_password", + "8280": "aes_password", + "8130": "aes_password", + "8303": "aes_password", + "8610": "aes_password", + "8361": "aes_password", + "8339": "aes_password", + "8037": "aes_password", + "8102": "aes_password", + "7845": "aes_password", + "7307": "aes_password", + "8607": "aes_password", + "8523": "aes_password", + "7839": "aes_password", + "7279": "aes_password", + "7321": "aes_password", + "8032": "aes_password", + "8894": "aes_password", + "8166": "aes_password", + "7381": "aes_password", + "8113": "aes_password", + "8139": "aes_password", + "8290": "aes_password", + "7990": "aes_password", + "7388": "aes_password", + "8571": "aes_password", + "8730": "aes_password", + "8441": "aes_password", + "8074": "aes_password", + "7813": "aes_password", + "8555": "aes_password", + "8978": "aes_password", + "7835": "aes_password", + "7323": "aes_password", + "7293": "aes_password", + "8550": "aes_password", + "7617": "aes_password", + "8071": "aes_password", + "7998": "aes_password", + "8115": "aes_password", + "7419": "aes_password", + "8825": "aes_password", + "8412": "aes_password", + "8019": "aes_password", + "8142": "aes_password", + "8186": "aes_password", + "8909": "aes_password", + "8078": "aes_password", + "8952": "aes_password", + "8360": "aes_password", + "8336": "aes_password", + "7953": "aes_password", + "7005": "aes_password", + "8663": "aes_password", + "8866": "aes_password", + "7950": "aes_password", + "7248": "aes_password", + "8519": "aes_password", + "8099": "aes_password", + "8151": "aes_password", + "8959": "aes_password", + "7042": "aes_password", + "7939": "aes_password", + "8064": "aes_password", + "8165": "aes_password", + "8836": "aes_password", + "8965": "aes_password", + "7431": "aes_password", + "7223": "aes_password", + "7999": "aes_password", + "8913": "aes_password", + "7921": "aes_password", + "8883": "aes_password", + "8169": "aes_password", + "7441": "aes_password", + "7469": "aes_password", + "7666": "aes_password", + "8547": "aes_password", + "7993": "aes_password", + "7705": "aes_password", + "8103": "aes_password", + "8524": "aes_password", + "8240": "aes_password", + "7779": "aes_password", + "7344": "aes_password", + "7395": "aes_password", + "8420": "aes_password", + "7287": "aes_password", + "7926": "aes_password", + "8100": "aes_password", + "8874": "aes_password", + "7496": "aes_password", + "7626": "aes_password", + "7784": "aes_password", + "7880": "aes_password", + "7226": "aes_password", + "8405": "aes_password", + "7910": "aes_password", + "8693": "aes_password", + "7997": "aes_password", + "8585": "aes_password", + "8383": "aes_password", + "8429": "aes_password", + "7878": "aes_password", + "8098": "aes_password", + "7288": "aes_password", + "8509": "aes_password", + "8809": "aes_password", + "7973": "aes_password", + "7620": "aes_password", + "7115": "aes_password", + "8445": "aes_password", + "8977": "aes_password", + "8341": "aes_password", + "7859": "aes_password", + "8256": "aes_password", + "7119": "aes_password", + "8442": "aes_password", + "8606": "aes_password", + "7992": "aes_password", + "7270": "aes_password", + "8988": "aes_password", + "7375": "aes_password", + "7747": "aes_password", + "7100": "aes_password", + "7639": "aes_password", + "7296": "aes_password", + "7435": "aes_password", + "7889": "aes_password", + "7636": "aes_password", + "7946": "aes_password", + "7819": "aes_password", + "7978": "aes_password", + "7728": "aes_password", + "8152": "aes_password", + "7660": "aes_password", + "7464": "aes_password", + "8398": "aes_password", + "8804": "aes_password", + "7450": "aes_password", + "8020": "aes_password", + "7988": "aes_password", + "7398": "aes_password", + "7171": "aes_password", + "8315": "aes_password", + "7871": "aes_password", + "8513": "aes_password", + "7250": "aes_password", + "8181": "aes_password", + "7793": "aes_password", + "7414": "aes_password", + "7179": "aes_password", + "7445": "aes_password", + "7259": "aes_password", + "8779": "aes_password", + "7695": "aes_password", + "7149": "aes_password", + "8973": "aes_password", + "8258": "aes_password", + "7291": "aes_password", + "8301": "aes_password", + "7447": "aes_password", + "8276": "aes_password", + "8279": "aes_password", + "7572": "aes_password", + "7146": "aes_password", + "7764": "aes_password", + "7504": "aes_password", + "7604": "aes_password", + "7465": "aes_password", + "7565": "aes_password", + "8976": "aes_password", + "8016": "aes_password", + "7707": "aes_password", + "7627": "aes_password", + "8379": "aes_password", + "7523": "aes_password", + "7145": "aes_password", + "7971": "aes_password", + "8790": "aes_password", + "8475": "aes_password", + "7718": "aes_password", + "7481": "aes_password", + "7650": "aes_password", + "7808": "aes_password", + "7142": "aes_password", + "8816": "aes_password", + "7676": "aes_password", + "7873": "aes_password", + "7885": "aes_password", + "7049": "aes_password", + "8994": "aes_password", + "8863": "aes_password", + "7178": "aes_password", + "8625": "aes_password", + "7237": "aes_password", + "7936": "aes_password", + "7575": "aes_password", + "7673": "aes_password", + "7689": "aes_password", + "7455": "aes_password", + "7780": "aes_password", + "8687": "aes_password", + "7675": "aes_password", + "8431": "aes_password", + "7030": "aes_password", + "8756": "aes_password", + "8318": "aes_password", + "7088": "aes_password", + "8792": "aes_password", + "7454": "aes_password", + "7776": "aes_password", + "8531": "aes_password", + "8829": "aes_password", + "8350": "aes_password", + "7198": "aes_password", + "7438": "aes_password", + "8543": "aes_password", + "8981": "aes_password", + "8915": "aes_password", + "8446": "aes_password", + "7812": "aes_password", + "7570": "aes_password", + "7054": "aes_password", + "8903": "aes_password", + "7213": "aes_password", + "8670": "aes_password", + "7211": "aes_password", + "7327": "aes_password", + "7121": "aes_password", + "8926": "aes_password", + "7430": "aes_password", + "8413": "aes_password", + "7337": "aes_password", + "7771": "aes_password", + "7350": "aes_password", + "7284": "aes_password", + "8252": "aes_password", + "7138": "aes_password", + "7491": "aes_password", + "8774": "aes_password", + "8828": "aes_password", + "7069": "aes_password", + "7545": "aes_password", + "7360": "aes_password", + "8771": "aes_password", + "7915": "aes_password", + "8485": "aes_password", + "7002": "aes_password", + "8838": "aes_password", + "7040": "aes_password", + "8820": "aes_password", + "8033": "aes_password", + "7053": "aes_password", + "8640": "aes_password", + "8552": "aes_password", + "8180": "aes_password", + "7361": "aes_password", + "7560": "aes_password", + "8277": "aes_password", + "7243": "aes_password", + "8999": "aes_password", + "7391": "aes_password", + "8930": "aes_password", + "8957": "aes_password", + "8504": "aes_password", + "7122": "aes_password", + "7139": "aes_password", + "8789": "aes_password", + "8245": "aes_password", + "7493": "aes_password", + "8810": "aes_password", + "8521": "aes_password", + "7317": "aes_password", + "7476": "aes_password", + "8562": "aes_password", + "7724": "aes_password", + "8652": "aes_password", + "7434": "aes_password", + "8110": "aes_password", + "8533": "aes_password", + "7970": "aes_password", + "8745": "aes_password", + "7585": "aes_password", + "7085": "aes_password", + "8421": "aes_password", + "8370": "aes_password", + "7289": "aes_password", + "7996": "aes_password", + "8840": "aes_password", + "7194": "aes_password", + "7256": "aes_password", + "8609": "aes_password", + "8604": "aes_password", + "7062": "aes_password", + "7252": "aes_password", + "8076": "aes_password", + "7647": "aes_password", + "8271": "aes_password", + "7684": "aes_password", + "8539": "aes_password", + "8588": "aes_password", + "8611": "aes_password", + "8654": "aes_password", + "7836": "aes_password", + "8351": "aes_password", + "7962": "aes_password", + "8230": "aes_password", + "7332": "aes_password", + "7573": "aes_password", + "7825": "aes_password", + "7934": "aes_password", + "8572": "aes_password", + "8700": "aes_password", + "8328": "aes_password", + "8046": "aes_password", + "7425": "aes_password", + "8729": "aes_password", + "8886": "aes_password", + "7806": "aes_password", + "7239": "aes_password", + "8601": "aes_password", + "8717": "aes_password", + "7697": "aes_password", + "8352": "aes_password", + "8123": "aes_password", + "7205": "aes_password", + "8298": "aes_password", + "7490": "aes_password", + "7023": "aes_password", + "7964": "aes_password", + "8986": "aes_password", + "7101": "aes_password", + "7634": "aes_password", + "7269": "aes_password", + "8000": "aes_password", + "8172": "aes_password", + "7172": "aes_password", + "8107": "aes_password", + "8486": "aes_password", + "8716": "aes_password", + "8961": "aes_password", + "7685": "aes_password", + "7667": "aes_password", + "7359": "aes_password", + "7804": "aes_password", + "7012": "aes_password", + "8975": "aes_password", + "7598": "aes_password", + "7662": "aes_password", + "7043": "aes_password", + "8331": "aes_password", + "8617": "aes_password", + "7015": "aes_password", + "8390": "aes_password", + "8608": "aes_password", + "7346": "aes_password", + "7331": "aes_password", + "8984": "aes_password", + "7339": "aes_password", + "8465": "aes_password", + "7184": "aes_password", + "7851": "aes_password", + "7868": "aes_password", + "7663": "aes_password", + "8849": "aes_password", + "8667": "aes_password", + "7506": "aes_password", + "7351": "aes_password", + "7109": "aes_password", + "7426": "aes_password", + "7453": "aes_password", + "7737": "aes_password", + "7870": "aes_password", + "8308": "aes_password", + "7543": "aes_password", + "7299": "aes_password", + "8340": "aes_password", + "8243": "aes_password", + "7357": "aes_password", + "8641": "aes_password", + "7553": "aes_password", + "8464": "aes_password", + "7966": "aes_password", + "8581": "aes_password", + "7343": "aes_password", + "8759": "aes_password", + "7631": "aes_password", + "7181": "aes_password", + "7415": "aes_password", + "8187": "aes_password", + "8924": "aes_password", + "7452": "aes_password", + "8797": "aes_password", + "8525": "aes_password", + "7026": "aes_password", + "7322": "aes_password", + "7982": "aes_password", + "8906": "aes_password", + "8342": "aes_password", + "7977": "aes_password", + "7059": "aes_password", + "7986": "aes_password", + "7513": "aes_password", + "7134": "aes_password", + "7462": "aes_password", + "7800": "aes_password", + "8966": "aes_password", + "8453": "aes_password", + "7760": "aes_password", + "7155": "aes_password", + "8632": "aes_password", + "7679": "aes_password", + "8808": "aes_password", + "8508": "aes_password", + "7479": "aes_password", + "7263": "aes_password", + "8648": "aes_password", + "7972": "aes_password", + "8215": "aes_password", + "8948": "aes_password", + "7894": "aes_password", + "7364": "aes_password", + "8520": "aes_password", + "8754": "aes_password", + "7773": "aes_password", + "7255": "aes_password", + "7413": "aes_password", + "7524": "aes_password", + "8511": "aes_password", + "8914": "aes_password", + "8557": "aes_password", + "8085": "aes_password", + "7938": "aes_password", + "8549": "aes_password", + "8839": "aes_password", + "7628": "aes_password", + "7852": "aes_password", + "8788": "aes_password", + "8680": "aes_password", + "8371": "aes_password", + "8378": "aes_password", + "7525": "aes_password", + "7338": "aes_password", + "8969": "aes_password", + "8895": "aes_password", + "7280": "aes_password", + "8827": "aes_password", + "7502": "aes_password", + "8126": "aes_password", + "7208": "aes_password", + "8643": "aes_password", + "7175": "aes_password", + "8183": "aes_password", + "7314": "aes_password", + "8818": "aes_password", + "8039": "aes_password", + "7392": "aes_password", + "7902": "aes_password", + "7576": "aes_password", + "8537": "aes_password", + "7618": "aes_password", + "7203": "aes_password", + "8306": "aes_password", + "7855": "aes_password", + "8778": "aes_password", + "7928": "aes_password", + "7866": "aes_password", + "8483": "aes_password", + "7152": "aes_password", + "7309": "aes_password", + "7222": "aes_password", + "8023": "aes_password", + "7619": "aes_password", + "8985": "aes_password", + "7643": "aes_password", + "7440": "aes_password", + "7646": "aes_password", + "7892": "aes_password", + "7061": "aes_password", + "8941": "aes_password", + "7846": "aes_password", + "8683": "aes_password", + "7829": "aes_password", + "8711": "aes_password", + "8235": "aes_password", + "8463": "aes_password", + "7899": "aes_password", + "8602": "aes_password", + "8114": "aes_password", + "7593": "aes_password", + "7385": "aes_password", + "7624": "aes_password", + "8659": "aes_password", + "7460": "aes_password", + "7917": "aes_password", + "8705": "aes_password", + "7713": "aes_password", + "7875": "aes_password", + "7788": "aes_password", + "7548": "aes_password", + "7076": "aes_password", + "8770": "aes_password", + "8506": "aes_password", + "8769": "aes_password", + "8197": "aes_password", + "7655": "aes_password", + "7247": "aes_password", + "8432": "aes_password", + "7783": "aes_password", + "8327": "aes_password", + "8120": "aes_password", + "7131": "aes_password", + "8268": "aes_password", + "7696": "aes_password", + "8783": "aes_password", + "8406": "aes_password", + "8433": "aes_password", + "8024": "aes_password", + "8005": "aes_password", + "7097": "aes_password", + "8554": "aes_password", + "8967": "aes_password", + "8943": "aes_password", + "7792": "aes_password", + "8003": "aes_password", + "8698": "aes_password", + "7229": "aes_password", + "7687": "aes_password", + "8532": "aes_password", + "7304": "aes_password", + "8122": "aes_password", + "8202": "aes_password", + "7833": "aes_password", + "8679": "aes_password", + "7580": "aes_password", + "8498": "aes_password", + "8345": "aes_password", + "8696": "aes_password", + "7422": "aes_password", + "8650": "aes_password", + "8905": "aes_password", + "7495": "aes_password", + "7075": "aes_password", + "8912": "aes_password", + "8570": "aes_password", + "8847": "aes_password", + "8613": "aes_password", + "7551": "aes_password", + "7150": "aes_password", + "7218": "aes_password", + "7514": "aes_password", + "7195": "aes_password", + "8775": "aes_password", + "8757": "aes_password", + "8251": "aes_password", + "7209": "aes_password", + "7162": "aes_password", + "7564": "aes_password", + "8121": "aes_password", + "7960": "aes_password", + "7656": "aes_password", + "7397": "aes_password", + "8699": "aes_password", + "7421": "aes_password", + "8263": "aes_password", + "8598": "aes_password", + "7009": "aes_password", + "7861": "aes_password", + "7001": "aes_password", + "8744": "aes_password", + "8368": "aes_password", + "7975": "aes_password", + "7165": "aes_password", + "8530": "aes_password", + "8614": "aes_password", + "7258": "aes_password", + "8034": "aes_password", + "7449": "aes_password", + "7932": "aes_password", + "7906": "aes_password", + "8124": "aes_password", + "8526": "aes_password", + "8568": "aes_password", + "7754": "aes_password", + "8228": "aes_password", + "8904": "aes_password", + "8161": "aes_password", + "7547": "aes_password", + "7310": "aes_password", + "7558": "aes_password", + "8338": "aes_password", + "8799": "aes_password", + "8224": "aes_password", + "8563": "aes_password", + "7863": "aes_password", + "7682": "aes_password", + "8944": "aes_password", + "7914": "aes_password", + "7541": "aes_password", + "8953": "aes_password", + "7318": "aes_password", + "8560": "aes_password", + "8972": "aes_password", + "7608": "aes_password", + "8455": "aes_password", + "7468": "aes_password", + "7037": "aes_password", + "7690": "aes_password", + "8814": "aes_password", + "8664": "aes_password", + "7756": "aes_password", + "7569": "aes_password", + "7102": "aes_password", + "8119": "aes_password", + "7161": "aes_password", + "7378": "aes_password", + "7790": "aes_password", + "7638": "aes_password", + "7678": "aes_password", + "7483": "aes_password", + "7063": "aes_password", + "8348": "aes_password", + "7974": "aes_password", + "8596": "aes_password", + "8653": "aes_password", + "8319": "aes_password", + "8933": "aes_password", + "8877": "aes_password", + "7886": "aes_password", + "8459": "aes_password", + "8008": "aes_password", + "8631": "aes_password", + "8482": "aes_password", + "8868": "aes_password", + "7031": "aes_password", + "8036": "aes_password", + "7530": "aes_password", + "7498": "aes_password", + "8497": "aes_password", + "8505": "aes_password", + "8323": "aes_password", + "7566": "aes_password", + "7038": "aes_password", + "7640": "aes_password", + "8690": "aes_password", + "8150": "aes_password", + "7235": "aes_password", + "8921": "aes_password", + "8196": "aes_password", + "8938": "aes_password", + "7531": "aes_password", + "8002": "aes_password", + "7439": "aes_password", + "7451": "aes_password", + "7583": "aes_password", + "7400": "aes_password", + "7242": "aes_password", + "8589": "aes_password", + "8826": "aes_password", + "8050": "aes_password", + "8960": "aes_password", + "7409": "aes_password", + "8873": "aes_password", + "8710": "aes_password", + "8932": "aes_password", + "7751": "aes_password", + "8846": "aes_password", + "7420": "aes_password", + "8817": "aes_password", + "7884": "aes_password", + "7124": "aes_password", + "8047": "aes_password", + "8081": "aes_password", + "8144": "aes_password", + "7477": "aes_password", + "7066": "aes_password", + "7862": "aes_password", + "8389": "aes_password", + "8321": "aes_password", + "8239": "aes_password", + "8987": "aes_password", + "7032": "aes_password", + "7522": "aes_password", + "7691": "aes_password", + "8567": "aes_password", + "8425": "aes_password", + "8128": "aes_password", + "8853": "aes_password", + "7092": "aes_password", + "8624": "aes_password", + "7416": "aes_password", + "7461": "aes_password", + "7283": "aes_password", + "7463": "aes_password", + "8956": "aes_password", + "8574": "aes_password", + "8872": "aes_password", + "8480": "aes_password", + "8384": "aes_password", + "8540": "aes_password", + "8381": "aes_password", + "7064": "aes_password", + "8095": "aes_password", + "8349": "aes_password", + "8855": "aes_password", + "7881": "aes_password", + "7519": "aes_password", + "7961": "aes_password", + "8728": "aes_password", + "8222": "aes_password", + "8326": "aes_password", + "8218": "aes_password", + "8723": "aes_password", + "7068": "aes_password", + "7202": "aes_password", + "7923": "aes_password", + "7347": "aes_password", + "8794": "aes_password", + "7264": "aes_password", + "7437": "aes_password", + "8494": "aes_password", + "7335": "aes_password", + "7717": "aes_password", + "7295": "aes_password", + "8061": "aes_password", + "8702": "aes_password", + "7824": "aes_password", + "8111": "aes_password", + "7176": "aes_password", + "8332": "aes_password", + "8556": "aes_password", + "7818": "aes_password", + "8583": "aes_password", + "8920": "aes_password", + "8006": "aes_password", + "8108": "aes_password", + "7308": "aes_password", + "8708": "aes_password", + "7587": "aes_password", + "7219": "aes_password", + "7367": "aes_password", + "8681": "aes_password", + "8945": "aes_password", + "8337": "aes_password", + "7586": "aes_password", + "7095": "aes_password", + "8880": "aes_password", + "8388": "aes_password", + "7065": "aes_password", + "7045": "aes_password", + "7111": "aes_password", + "8762": "aes_password", + "7703": "aes_password", + "8686": "aes_password", + "8367": "aes_password", + "8796": "aes_password", + "7840": "aes_password", + "7945": "aes_password", + "8329": "aes_password", + "7107": "aes_password", + "8167": "aes_password", + "8862": "aes_password", + "8951": "aes_password", + "7963": "aes_password", + "8073": "aes_password", + "8068": "aes_password", + "7759": "aes_password", + "8175": "aes_password", + "7704": "aes_password", + "8083": "aes_password", + "7206": "aes_password", + "8054": "aes_password", + "7160": "aes_password", + "8727": "aes_password", + "7333": "aes_password", + "7094": "aes_password", + "8753": "aes_password", + "7755": "aes_password", + "8577": "aes_password", + "7099": "aes_password", + "7334": "aes_password", + "8055": "aes_password", + "8397": "aes_password", + "8214": "aes_password", + "8366": "aes_password", + "8541": "aes_password", + "7312": "aes_password", + "8283": "aes_password", + "8052": "aes_password", + "7653": "aes_password", + "7402": "aes_password", + "7224": "aes_password", + "7217": "aes_password", + "7126": "aes_password", + "7710": "aes_password", + "8668": "aes_password", + "8401": "aes_password", + "8628": "aes_password", + "8424": "aes_password", + "7658": "aes_password", + "7471": "aes_password", + "8813": "aes_password", + "7374": "aes_password", + "8067": "aes_password", + "8703": "aes_password", + "7681": "aes_password", + "7382": "aes_password", + "8538": "aes_password", + "8676": "aes_password", + "7033": "aes_password", + "7480": "aes_password", + "8841": "aes_password", + "8834": "aes_password", + "8666": "aes_password", + "8084": "aes_password", + "7925": "aes_password", + "7742": "aes_password", + "8669": "aes_password", + "7532": "aes_password", + "8657": "aes_password", + "8358": "aes_password", + "8590": "aes_password", + "7746": "aes_password", + "7605": "aes_password", + "7905": "aes_password", + "8294": "aes_password", + "8980": "aes_password", + "8735": "aes_password", + "8179": "aes_password", + "8760": "aes_password", + "7036": "aes_password", + "7404": "aes_password", + "8062": "aes_password", + "8201": "aes_password", + "7320": "aes_password", + "8964": "aes_password", + "8402": "aes_password", + "7303": "aes_password", + "8765": "aes_password", + "7803": "aes_password", + "8247": "aes_password", + "8449": "aes_password", + "7278": "aes_password", + "7189": "aes_password", + "8284": "aes_password", + "8848": "aes_password", + "7809": "aes_password", + "7503": "aes_password", + "8419": "aes_password", + "7723": "aes_password", + "7446": "aes_password", + "7556": "aes_password", + "8739": "aes_password", + "8514": "aes_password", + "7486": "aes_password", + "7143": "aes_password", + "8709": "aes_password", + "7105": "aes_password", + "7654": "aes_password", + "8436": "aes_password", + "8761": "aes_password", + "7595": "aes_password", + "8209": "aes_password", + "8029": "aes_password", + "7603": "aes_password", + "8257": "aes_password", + "8649": "aes_password", + "7370": "aes_password", + "8534": "aes_password", + "7267": "aes_password", + "8565": "aes_password", + "7664": "aes_password", + "8551": "aes_password", + "7888": "aes_password", + "8576": "aes_password", + "8893": "aes_password", + "8857": "aes_password", + "7599": "aes_password", + "7568": "aes_password", + "7791": "aes_password", + "7916": "aes_password", + "8885": "aes_password", + "7418": "aes_password", + "7245": "aes_password", + "8170": "aes_password", + "8466": "aes_password", + "7098": "aes_password", + "7489": "aes_password", + "8997": "aes_password", + "8014": "aes_password", + "8805": "aes_password", + "7774": "aes_password", + "8325": "aes_password", + "8262": "aes_password", + "8038": "aes_password", + "8512": "aes_password", + "7546": "aes_password", + "7614": "aes_password", + "8309": "aes_password", + "7123": "aes_password", + "8324": "aes_password", + "8591": "aes_password", + "8255": "aes_password", + "7623": "aes_password", + "8270": "aes_password", + "8715": "aes_password", + "8515": "aes_password", + "8088": "aes_password", + "8375": "aes_password", + "7693": "aes_password", + "8752": "aes_password", + "8059": "aes_password", + "7271": "aes_password", + "8917": "aes_password", + "7500": "aes_password", + "8656": "aes_password", + "7022": "aes_password", + "8812": "aes_password", + "8858": "aes_password", + "7980": "aes_password", + "7090": "aes_password", + "7637": "aes_password", + "7671": "aes_password", + "7904": "aes_password", + "8629": "aes_password", + "7034": "aes_password", + "7207": "aes_password", + "7096": "aes_password", + "7621": "aes_password", + "7770": "aes_password", + "8221": "aes_password", + "7077": "aes_password", + "7661": "aes_password", + "7994": "aes_password", + "7632": "aes_password", + "7848": "aes_password", + "8822": "aes_password", + "7328": "aes_password", + "8438": "aes_password", + "8939": "aes_password", + "8636": "aes_password", + "7995": "aes_password", + "8363": "aes_password", + "7010": "aes_password", + "7212": "aes_password", + "8559": "aes_password", + "8548": "aes_password", + "7275": "aes_password", + "8819": "aes_password", + "7927": "aes_password", + "8291": "aes_password", + "7652": "aes_password", + "7954": "aes_password", + "7832": "aes_password", + "8087": "aes_password", + "8646": "aes_password", + "7736": "aes_password", + "7581": "aes_password", + "7216": "aes_password", + "7190": "aes_password", + "7180": "aes_password", + "7412": "aes_password", + "8569": "aes_password", + "8010": "aes_password", + "7529": "aes_password", + "8118": "aes_password", + "8162": "aes_password", + "8925": "aes_password", + "8490": "aes_password", + "7762": "aes_password", + "7610": "aes_password", + "8439": "aes_password", + "7698": "aes_password", + "7648": "aes_password", + "8742": "aes_password", + "8993": "aes_password", + "7785": "aes_password", + "7609": "aes_password", + "7896": "aes_password", + "8851": "aes_password", + "7731": "aes_password", + "8248": "aes_password", + "8386": "aes_password", + "8621": "aes_password", + "7349": "aes_password", + "7366": "aes_password", + "7169": "aes_password", + "7942": "aes_password", + "7633": "aes_password", + "7781": "aes_password", + "8387": "aes_password", + "8117": "aes_password", + "8782": "aes_password", + "8192": "aes_password", + "8104": "aes_password", + "7615": "aes_password", + "7050": "aes_password", + "7315": "aes_password", + "7725": "aes_password", + "7058": "aes_password", + "7865": "aes_password", + "7133": "aes_password", + "8458": "aes_password", + "7552": "aes_password", + "7761": "aes_password", + "8229": "aes_password", + "7677": "aes_password", + "8619": "aes_password", + "8193": "aes_password", + "8164": "aes_password", + "8493": "aes_password", + "7943": "aes_password", + "8476": "aes_password", + "8427": "aes_password", + "7240": "aes_password", + "8773": "aes_password", + "8749": "aes_password", + "8983": "aes_password", + "8910": "aes_password", + "7722": "aes_password", + "8092": "aes_password", + "8086": "aes_password", + "7911": "aes_password", + "8793": "aes_password", + "8725": "aes_password", + "7201": "aes_password", + "7538": "aes_password", + "7016": "aes_password", + "8396": "aes_password", + "7726": "aes_password", + "8495": "aes_password", + "8300": "aes_password", + "7948": "aes_password", + "8237": "aes_password", + "8802": "aes_password", + "7074": "aes_password", + "8077": "aes_password", + "7516": "aes_password", + "8908": "aes_password", + "8616": "aes_password", + "7814": "aes_password", + "8688": "aes_password", + "7236": "aes_password", + "7537": "aes_password", + "8691": "aes_password", + "8109": "aes_password", + "8223": "aes_password", + "7895": "aes_password", + "8018": "aes_password", + "7820": "aes_password", + "7407": "aes_password", + "8544": "aes_password", + "7651": "aes_password", + "8288": "aes_password", + "7930": "aes_password", + "8004": "aes_password", + "8935": "aes_password", + "7272": "aes_password", + "8707": "aes_password", + "8907": "aes_password", + "8146": "aes_password", + "7850": "aes_password", + "8579": "aes_password", + "7125": "aes_password", + "8692": "aes_password", + "7828": "aes_password", + "7113": "aes_password", + "7027": "aes_password", + "8148": "aes_password", + "7078": "aes_password", + "7508": "aes_password", + "8434": "aes_password", + "8374": "aes_password", + "8356": "aes_password", + "8942": "aes_password", + "8437": "aes_password", + "8902": "aes_password", + "7887": "aes_password", + "7281": "aes_password", + "8593": "aes_password", + "7853": "aes_password", + "8898": "aes_password", + "8927": "aes_password", + "8184": "aes_password", + "7879": "aes_password", + "8409": "aes_password", + "8241": "aes_password", + "7013": "aes_password", + "8145": "aes_password", + "8094": "aes_password", + "8450": "aes_password", + "7353": "aes_password", + "7286": "aes_password", + "7393": "aes_password", + "8852": "aes_password", + "8456": "aes_password", + "8660": "aes_password", + "7908": "aes_password", + "7408": "aes_password", + "8784": "aes_password", + "8990": "aes_password", + "8899": "aes_password", + "8748": "aes_password", + "8385": "aes_password", + "7151": "aes_password", + "7355": "aes_password", + "8051": "aes_password", + "7842": "aes_password", + "7985": "aes_password", + "7081": "aes_password", + "8620": "aes_password", + "7924": "aes_password", + "7305": "aes_password", + "8949": "aes_password", + "7091": "aes_password", + "7947": "aes_password", + "8075": "aes_password", + "8721": "aes_password", + "8605": "aes_password", + "8305": "aes_password", + "7185": "aes_password", + "7831": "aes_password", + "8672": "aes_password", + "8253": "aes_password", + "8274": "aes_password", + "8746": "aes_password", + "8738": "aes_password", + "8015": "aes_password", + "8007": "aes_password", + "8768": "aes_password", + "7492": "aes_password", + "7752": "aes_password", + "8391": "aes_password", + "7596": "aes_password", + "7797": "aes_password", + "8940": "aes_password", + "8304": "aes_password", + "7706": "aes_password", + "7060": "aes_password", + "7251": "aes_password", + "7472": "aes_password", + "7340": "aes_password", + "7607": "aes_password", + "7215": "aes_password", + "7136": "aes_password", + "8171": "aes_password", + "8740": "aes_password", + "8343": "aes_password", + "7024": "aes_password", + "7635": "aes_password", + "8689": "aes_password", + "7158": "aes_password", + "8320": "aes_password", + "8516": "aes_password", + "8238": "aes_password", + "8156": "aes_password", + "8149": "aes_password", + "8199": "aes_password", + "7822": "aes_password", + "7517": "aes_password", + "8191": "aes_password" + }, + "method": "aes-256-cfb", + "timeout": 60 +} \ No newline at end of file From 80102f38995de506a1ca6c18b01303fbdf6910ee Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 18:57:44 +0800 Subject: [PATCH 074/104] fix pyflakes --- shadowsocks/asyncdns.py | 1 - shadowsocks/udprelay.py | 1 - tests/gen_multiple_passwd.py | 1 - 3 files changed, 3 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index e60a383..ea705b2 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -18,7 +18,6 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import time import os import socket import struct diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 7e5e2c3..b67770a 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -62,7 +62,6 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import time import socket import logging import struct diff --git a/tests/gen_multiple_passwd.py b/tests/gen_multiple_passwd.py index a058582..62586c2 100644 --- a/tests/gen_multiple_passwd.py +++ b/tests/gen_multiple_passwd.py @@ -1,6 +1,5 @@ #!/usr/bin/python -import os import json with open('server-multi-passwd-performance.json', 'wb') as f: From d319fab5cac62ac4676fa271700e287f27bcf84e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 18:58:57 +0800 Subject: [PATCH 075/104] fix asyncdns unit test --- shadowsocks/asyncdns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index ea705b2..f5d398a 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -433,7 +433,7 @@ class DNSResolver(object): def test(): dns_resolver = DNSResolver() loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop, ref=True) + dns_resolver.add_to_loop(loop) global counter counter = 0 @@ -448,6 +448,7 @@ def test(): counter += 1 if counter == 9: dns_resolver.close() + loop.stop() a_callback = callback return a_callback From e8b29469993e0038befc0625f4ccc9c750b43a6d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 19:09:29 +0800 Subject: [PATCH 076/104] fix workers --- shadowsocks/asyncdns.py | 4 +++- shadowsocks/tcprelay.py | 3 +++ shadowsocks/udprelay.py | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index f5d398a..d807caf 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -425,7 +425,9 @@ class DNSResolver(object): def close(self): if self._sock: - self._loop.remove(self._sock, self) + if self._loop: + self._loop.remove_periodic(self.handle_periodic) + self._loop.remove(self._sock, self) self._sock.close() self._sock = None diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 3300df3..38d4101 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -703,4 +703,7 @@ class TCPRelay(object): def close(self, next_tick=False): self._closed = True if not next_tick: + if self._eventloop: + self._eventloop.remove_periodic(self.handle_periodic) + self._eventloop.remove(self._server_socket, self) self._server_socket.close() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index b67770a..c2386d9 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -278,10 +278,11 @@ class UDPRelay(object): self._server_socket.close() for sock in self._sockets: sock.close() - self._eventloop.remove_periodic(self.handle_periodic) def close(self, next_tick=False): self._closed = True if not next_tick: - self._eventloop.remove(self._server_socket, self) + if self._eventloop: + self._eventloop.remove_periodic(self.handle_periodic) + self._eventloop.remove(self._server_socket, self) self._server_socket.close() From 111acf66c1dc3baacaf0004ed0b72a9c74102423 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 14:37:44 +0800 Subject: [PATCH 077/104] fix graceful restart and add unit test --- shadowsocks/eventloop.py | 6 ++-- shadowsocks/tcprelay.py | 7 ++-- shadowsocks/udprelay.py | 12 ++++--- tests/graceful.json | 10 ++++++ tests/graceful_cli.py | 18 ++++++++++ tests/graceful_server.py | 13 +++++++ tests/jenkins.sh | 1 + tests/test_graceful_restart.sh | 63 ++++++++++++++++++++++++++++++++++ 8 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 tests/graceful.json create mode 100644 tests/graceful_cli.py create mode 100644 tests/graceful_server.py create mode 100755 tests/test_graceful_restart.sh diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 09e9761..05927d3 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -192,7 +192,7 @@ class EventLoop(object): def run(self): events = [] while not self._stopping: - now = time.time() + asap = False try: events = self.poll(TIMEOUT_PRECISION) except (OSError, IOError) as e: @@ -200,6 +200,7 @@ class EventLoop(object): # EPIPE: Happens when the client closes the connection # EINTR: Happens when received a signal # handles them as soon as possible + asap = True logging.debug('poll:%s', e) else: logging.error('poll:%s', e) @@ -214,7 +215,8 @@ class EventLoop(object): handler.handle_event(sock, fd, event) except (OSError, IOError) as e: shell.print_exception(e) - if now - self._last_time >= TIMEOUT_PRECISION: + now = time.time() + if asap or now - self._last_time >= TIMEOUT_PRECISION: for callback in self._periodic_callbacks: callback() self._last_time = now diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 38d4101..516da32 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -689,18 +689,19 @@ class TCPRelay(object): logging.warn('poll removed fd') def handle_periodic(self): - self._sweep_timeout() if self._closed: if self._server_socket: self._eventloop.remove(self._server_socket, self) - self._eventloop.remove_periodic(self.handle_periodic) self._server_socket.close() self._server_socket = None - logging.info('closed listen port %d', self._listen_port) + logging.info('closed TCP port %d', self._listen_port) if not self._fd_to_handlers: + logging.info('stopping') self._eventloop.stop() + self._sweep_timeout() def close(self, next_tick=False): + logging.debug('TCP close') self._closed = True if not next_tick: if self._eventloop: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index c2386d9..99e9ed3 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -272,14 +272,18 @@ class UDPRelay(object): self._handle_client(sock) def handle_periodic(self): + if self._closed: + if self._server_socket: + logging.info('closed UDP port %d', self._listen_port) + self._server_socket.close() + self._server_socket = None + for sock in self._sockets: + sock.close() self._cache.sweep() self._client_fd_to_server_addr.sweep() - if self._closed: - self._server_socket.close() - for sock in self._sockets: - sock.close() def close(self, next_tick=False): + logging.debug('UDP close') self._closed = True if not next_tick: if self._eventloop: diff --git a/tests/graceful.json b/tests/graceful.json new file mode 100644 index 0000000..4b95434 --- /dev/null +++ b/tests/graceful.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8387, + "local_port":1081, + "password":"aes_password", + "timeout":15, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/graceful_cli.py b/tests/graceful_cli.py new file mode 100644 index 0000000..ef6110e --- /dev/null +++ b/tests/graceful_cli.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import socket +import socks +import time + + +SERVER_IP = '127.0.0.1' +SERVER_PORT = 8001 + + +if __name__ == '__main__': + s = socks.socksocket() + s.set_proxy(socks.SOCKS5, SERVER_IP, 1081) + s.connect((SERVER_IP, SERVER_PORT)) + s.send(b'test') + time.sleep(30) + s.close() diff --git a/tests/graceful_server.py b/tests/graceful_server.py new file mode 100644 index 0000000..d7038f1 --- /dev/null +++ b/tests/graceful_server.py @@ -0,0 +1,13 @@ +#!/usr/bin/python + +import socket + + +if __name__ == '__main__': + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('127.0.0.1', 8001)) + s.listen(1024) + c = None + while True: + c = s.accept() diff --git a/tests/jenkins.sh b/tests/jenkins.sh index ea5c163..32dd30e 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -69,6 +69,7 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh +run_test tests/test_graceful_restart.sh run_test tests/test_udp_src.sh run_test tests/test_command.sh diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh new file mode 100755 index 0000000..cec984a --- /dev/null +++ b/tests/test_graceful_restart.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +PYTHON="coverage run -p -a" +URL=http://127.0.0.1/file + + +# setup processes +$PYTHON shadowsocks/local.py -c tests/graceful.json & +LOCAL=$! + +$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & +SERVER=$! + +python tests/graceful_server.py & +GSERVER=$! + +sleep 1 + +python tests/graceful_cli.py & +GCLI=$! + +sleep 1 + +# graceful restart server: send SIGQUIT to old process and start a new one +kill -s SIGQUIT $SERVER +$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & +NEWSERVER=$! + +sleep 1 + +# check old server +ps x | grep -v grep | grep $SERVER +OLD_SERVER_RUNNING1=$? +# old server should not quit at this moment +echo old server running: $OLD_SERVER_RUNNING1 + +sleep 1 + +# close connections on old server +kill -s SIGINT $GCLI +kill -s SIGKILL $GSERVER +kill -s SIGINT $LOCAL + +sleep 11 + +# check old server +ps x | grep -v grep | grep $SERVER +OLD_SERVER_RUNNING2=$? +# old server should quit at this moment +echo old server running: $OLD_SERVER_RUNNING2 + +# new server is expected running +kill -s SIGINT $NEWSERVER || exit 1 + +if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then + exit 1 +fi + +if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then + kill -s SIGINT $SERVER + sleep 1 + exit 1 +fi From 02120e3402cfbd940a20893646ec3417564a46f2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 14:45:15 +0800 Subject: [PATCH 078/104] optimize eventloop --- shadowsocks/asyncdns.py | 6 +++--- shadowsocks/eventloop.py | 20 ++++++++------------ shadowsocks/tcprelay.py | 8 ++++---- shadowsocks/udprelay.py | 4 ++-- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index d807caf..c5fc99d 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -357,7 +357,7 @@ class DNSResolver(object): return if event & eventloop.POLL_ERR: logging.error('dns socket err') - self._loop.remove(self._sock, self) + self._loop.remove(self._sock) self._sock.close() # TODO when dns server is IPv6 self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, @@ -380,7 +380,7 @@ class DNSResolver(object): del self._cb_to_hostname[callback] arr = self._hostname_to_cb.get(hostname, None) if arr: - arr.remove(callback, self) + arr.remove(callback) if not arr: del self._hostname_to_cb[hostname] if hostname in self._hostname_status: @@ -427,7 +427,7 @@ class DNSResolver(object): if self._sock: if self._loop: self._loop.remove_periodic(self.handle_periodic) - self._loop.remove(self._sock, self) + self._loop.remove(self._sock) self._sock.close() self._sock = None diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 05927d3..bf2f994 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -150,8 +150,7 @@ class EventLoop(object): else: raise Exception('can not find any available functions in select ' 'package') - self._fd_to_f = {} - self._fd_to_handler = {} + self._fdmap = {} # (f, handler) self._last_time = time.time() self._periodic_callbacks = [] self._stopping = False @@ -159,20 +158,17 @@ class EventLoop(object): def poll(self, timeout=None): events = self._impl.poll(timeout) - return [(self._fd_to_f[fd], fd, event) for fd, event in events] + return [(self._fdmap[fd][0], fd, event) for fd, event in events] def add(self, f, mode, handler): fd = f.fileno() - self._fd_to_f[fd] = f + self._fdmap[fd] = (f, handler) self._impl.register(fd, mode) - self._fd_to_handler[fd] = handler - def remove(self, f, handler): + def remove(self, f): fd = f.fileno() - del self._fd_to_f[fd] + del self._fdmap[fd] self._impl.unregister(fd) - if handler is not None: - del self._fd_to_handler[fd] def add_periodic(self, callback): self._periodic_callbacks.append(callback) @@ -182,9 +178,8 @@ class EventLoop(object): def modify(self, f, mode, handler): fd = f.fileno() + self._fdmap[fd] = (f, handler) self._impl.modify(fd, mode) - if handler is not None: - self._fd_to_handler[fd] = handler def stop(self): self._stopping = True @@ -209,8 +204,9 @@ class EventLoop(object): continue for sock, fd, event in events: - handler = self._fd_to_handler.get(fd, None) + handler = self._fdmap.get(fd, None) if handler is not None: + handler = handler[1] try: handler.handle_event(sock, fd, event) except (OSError, IOError) as e: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 516da32..d1118de 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -534,13 +534,13 @@ class TCPRelayHandler(object): logging.debug('destroy') if self._remote_sock: logging.debug('destroying remote') - self._loop.remove(self._remote_sock, self._server) + self._loop.remove(self._remote_sock) del self._fd_to_handlers[self._remote_sock.fileno()] self._remote_sock.close() self._remote_sock = None if self._local_sock: logging.debug('destroying local') - self._loop.remove(self._local_sock, self._server) + self._loop.remove(self._local_sock) del self._fd_to_handlers[self._local_sock.fileno()] self._local_sock.close() self._local_sock = None @@ -691,7 +691,7 @@ class TCPRelay(object): def handle_periodic(self): if self._closed: if self._server_socket: - self._eventloop.remove(self._server_socket, self) + self._eventloop.remove(self._server_socket) self._server_socket.close() self._server_socket = None logging.info('closed TCP port %d', self._listen_port) @@ -706,5 +706,5 @@ class TCPRelay(object): if not next_tick: if self._eventloop: self._eventloop.remove_periodic(self.handle_periodic) - self._eventloop.remove(self._server_socket, self) + self._eventloop.remove(self._server_socket) self._server_socket.close() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 99e9ed3..856fe08 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -135,7 +135,7 @@ class UDPRelay(object): def _close_client(self, client): if hasattr(client, 'close'): self._sockets.remove(client.fileno()) - self._eventloop.remove(client, self) + self._eventloop.remove(client) client.close() else: # just an address @@ -288,5 +288,5 @@ class UDPRelay(object): if not next_tick: if self._eventloop: self._eventloop.remove_periodic(self.handle_periodic) - self._eventloop.remove(self._server_socket, self) + self._eventloop.remove(self._server_socket) self._server_socket.close() From b28de8e2f1d92c3df01598c9738e001371034be2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 15:10:46 +0800 Subject: [PATCH 079/104] fix pyflakes --- tests/graceful_cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/graceful_cli.py b/tests/graceful_cli.py index ef6110e..e58674b 100644 --- a/tests/graceful_cli.py +++ b/tests/graceful_cli.py @@ -1,6 +1,5 @@ #!/usr/bin/python -import socket import socks import time From 177c639b191f87767505eb4322a1f6f3a976d11e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 15:12:41 +0800 Subject: [PATCH 080/104] fix graceful restart test --- shadowsocks/udprelay.py | 2 +- tests/graceful.json | 2 +- tests/test_graceful_restart.sh | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 856fe08..e3fe77c 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -274,11 +274,11 @@ class UDPRelay(object): def handle_periodic(self): if self._closed: if self._server_socket: - logging.info('closed UDP port %d', self._listen_port) self._server_socket.close() self._server_socket = None for sock in self._sockets: sock.close() + logging.info('closed UDP port %d', self._listen_port) self._cache.sweep() self._client_fd_to_server_addr.sweep() diff --git a/tests/graceful.json b/tests/graceful.json index 4b95434..7d94ea5 100644 --- a/tests/graceful.json +++ b/tests/graceful.json @@ -1,6 +1,6 @@ { "server":"127.0.0.1", - "server_port":8387, + "server_port":8388, "local_port":1081, "password":"aes_password", "timeout":15, diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh index cec984a..d91ba92 100755 --- a/tests/test_graceful_restart.sh +++ b/tests/test_graceful_restart.sh @@ -23,6 +23,7 @@ sleep 1 # graceful restart server: send SIGQUIT to old process and start a new one kill -s SIGQUIT $SERVER +sleep 0.5 $PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & NEWSERVER=$! @@ -37,7 +38,7 @@ echo old server running: $OLD_SERVER_RUNNING1 sleep 1 # close connections on old server -kill -s SIGINT $GCLI +kill -s SIGKILL $GCLI kill -s SIGKILL $GSERVER kill -s SIGINT $LOCAL @@ -49,6 +50,7 @@ OLD_SERVER_RUNNING2=$? # old server should quit at this moment echo old server running: $OLD_SERVER_RUNNING2 +kill -s SIGINT $SERVER # new server is expected running kill -s SIGINT $NEWSERVER || exit 1 @@ -57,7 +59,6 @@ if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then fi if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then - kill -s SIGINT $SERVER sleep 1 exit 1 fi From d946ac8213c1d523cad09dbfd8e1cbd5527fef50 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 17:33:13 +0800 Subject: [PATCH 081/104] skip graceful restart test on Jenkins --- tests/jenkins.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 32dd30e..a85c461 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -69,7 +69,11 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh -run_test tests/test_graceful_restart.sh + +if [ "a$JENKINS" != "a1" ] ; then + # jenkins blocked SIGQUIT with sigprocmask(), we have to skip this test on Jenkins + run_test tests/test_graceful_restart.sh +fi run_test tests/test_udp_src.sh run_test tests/test_command.sh From 999a54168e74fcd65d916532f70436e1f83a2761 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 17:45:48 +0800 Subject: [PATCH 082/104] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index ada1893..96840f3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.7 2015-08-02 +- Optimize speed for multiple ports + 2.6.11 2015-07-10 - Fix a compatibility issue in UDP Relay From 42111848863c2b8e86b00786a538824c72d257e8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 3 Aug 2015 01:40:18 +0800 Subject: [PATCH 083/104] remove unnecessary overwrite --- shadowsocks/eventloop.py | 3 +-- shadowsocks/tcprelay.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index bf2f994..b27afe3 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -176,9 +176,8 @@ class EventLoop(object): def remove_periodic(self, callback): self._periodic_callbacks.remove(callback) - def modify(self, f, mode, handler): + def modify(self, f, mode): fd = f.fileno() - self._fdmap[fd] = (f, handler) self._impl.modify(fd, mode) def stop(self): diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index d1118de..e61b8ac 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -173,14 +173,14 @@ class TCPRelayHandler(object): event |= eventloop.POLL_OUT if self._upstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN - self._loop.modify(self._local_sock, event, self._server) + self._loop.modify(self._local_sock, event) if self._remote_sock: event = eventloop.POLL_ERR if self._downstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN if self._upstream_status & WAIT_STATUS_WRITING: event |= eventloop.POLL_OUT - self._loop.modify(self._remote_sock, event, self._server) + self._loop.modify(self._remote_sock, event) def _write_to_sock(self, data, sock): # write data to sock From baad209160268d9fd176d5e98d43b4efb523177d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 3 Aug 2015 01:41:15 +0800 Subject: [PATCH 084/104] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 195b2bb..4a6e9f3 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.7", + version="2.7.1", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 58df1d82d01c3ba33f2d63bfd49fb1cfecde9205 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 3 Aug 2015 23:54:30 +0800 Subject: [PATCH 085/104] close poll object after loop stopped --- shadowsocks/eventloop.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index b27afe3..78b532c 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -98,6 +98,9 @@ class KqueueLoop(object): self.unregister(fd) self.register(fd, mode) + def close(self): + self._kqueue.close() + class SelectLoop(object): @@ -135,6 +138,9 @@ class SelectLoop(object): self.unregister(fd) self.register(fd, mode) + def close(self): + pass + class EventLoop(object): def __init__(self): @@ -216,6 +222,9 @@ class EventLoop(object): callback() self._last_time = now + def __del__(self): + self._impl.close() + # from tornado def errno_from_exception(e): From 956199efcdbc4a808b7fe0f0d69d12bb4b2835a2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 18:12:38 +0800 Subject: [PATCH 086/104] add control manager --- shadowsocks/manager.py | 152 ++++++++++++++++++++++++++++++++++++++++ shadowsocks/server.py | 12 +++- shadowsocks/shell.py | 5 +- shadowsocks/tcprelay.py | 2 + shadowsocks/udprelay.py | 2 + 5 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 shadowsocks/manager.py diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py new file mode 100644 index 0000000..199a96d --- /dev/null +++ b/shadowsocks/manager.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2015 clowwindy +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import errno +import traceback +import socket +import logging +import json +import collections + +from shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell + + +BUF_SIZE = 2048 + + +class Manager(object): + + def __init__(self, config): + self._config = config + self._relays = {} # (tcprelay, udprelay) + self._loop = eventloop.EventLoop() + self._dns_resolver = asyncdns.DNSResolver() + self._dns_resolver.add_to_loop(self._loop) + self._control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.IPPROTO_UDP) + self._statistics = collections.defaultdict(int) + self._control_client_addr = None + try: + self._control_socket.bind(('127.0.0.1', + int(config['manager_port']))) + self._control_socket.setblocking(False) + except (OSError, IOError) as e: + logging.error(e) + logging.error('can not bind to manager port') + exit(1) + self._loop.add(self._control_socket, + eventloop.POLL_IN, self) + + port_password = config['port_password'] + del config['port_password'] + for port, password in port_password.items(): + a_config = config.copy() + a_config['server_port'] = int(port) + a_config['password'] = password + self.add_port(a_config) + + def add_port(self, config): + port = int(config['server_port']) + servers = self._relays.get(port, None) + if servers: + logging.error("server already exists at %s:%d" % (config['server'], + port)) + return + logging.info("adding server at %s:%d" % (config['server'], port)) + t = tcprelay.TCPRelay(config, self._dns_resolver, False) + u = udprelay.UDPRelay(config, self._dns_resolver, False) + t.add_to_loop(self._loop) + u.add_to_loop(self._loop) + self._relays[port] = (t, u) + + def remove_port(self, config): + port = int(config['server_port']) + servers = self._relays.get(port, None) + if servers: + logging.info("removing server at %s:%d" % (config['server'], port)) + t, u = servers + t.close(next_tick=False) + u.close(next_tick=False) + del self._relays[port] + else: + logging.error("server not exist at %s:%d" % (config['server'], + port)) + + def handle_event(self, sock, fd, event): + if sock == self._control_socket and event == eventloop.POLL_IN: + data, self._control_client_addr = sock.recvfrom(BUF_SIZE) + parsed = self._parse_command(data) + if parsed: + command, config = parsed + a_config = self._config.copy() + if config: + a_config.update(config) + if 'server_port' not in a_config: + logging.error('can not find server_port in config') + else: + if command == 'add': + self.add_port(a_config) + elif command == 'remove': + self.remove_port(a_config) + elif command == 'ping': + self._send_control_data(b'pong') + else: + logging.error('unknown command %s', command) + + def _parse_command(self, data): + # commands: + # add: {"server_port": 8000, "password": "foobar"} + # remove: {"server_port": 8000"} + data = common.to_str(data) + parts = data.split(':', 1) + if len(parts) < 2: + return data, None + command, config_json = parts + try: + config = json.loads(config_json) + return command, config + except Exception as e: + logging.error(e) + return None + + def handle_periodic(self): + # TODO send statistics + pass + + def _send_control_data(self, data): + if self._control_client_addr: + try: + self._control_socket.sendto(data, self._control_client_addr) + except (socket.error, OSError, IOError) as e: + error_no = eventloop.errno_from_exception(e) + if error_no in (errno.EAGAIN, errno.EINPROGRESS, + errno.EWOULDBLOCK): + return + else: + shell.print_exception(e) + if self._config['verbose']: + traceback.print_exc() + + def run(self): + self._loop.run() + + +def run(config): + Manager(config).run() \ No newline at end of file diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 429a20a..8ff13d6 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -24,7 +24,8 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns +from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, \ + asyncdns, manager def main(): @@ -48,10 +49,17 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] + if config['manager_port']: + logging.info('entering manager mode') + manager.run(config) + return + tcp_servers = [] udp_servers = [] dns_resolver = asyncdns.DNSResolver() - for port, password in config['port_password'].items(): + port_password = config['port_password'] + del config['port_password'] + for port, password in port_password.items(): a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index f8ae81f..586c2f6 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -136,7 +136,7 @@ def get_config(is_local): else: shortopts = 'hd:s:p:k:m:c:t:vq' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user=', 'version'] + 'forbidden-ip=', 'user=', 'manager-port=', 'version'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -181,6 +181,8 @@ def get_config(is_local): config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) + elif key == '--manager-port': + config['manager_port'] = int(value) elif key == '--user': config['user'] = to_str(value) elif key == '--forbidden-ip': @@ -317,6 +319,7 @@ Proxy options: --fast-open use TCP_FASTOPEN, requires Linux 3.7+ --workers WORKERS number of workers, available on Unix/Linux --forbidden-ip IPLIST comma seperated IP list forbidden to connect + --manager-port PORT optional server manager UDP port General options: -h, --help show this help message and exit diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index e61b8ac..e14dcaa 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -708,3 +708,5 @@ class TCPRelay(object): self._eventloop.remove_periodic(self.handle_periodic) self._eventloop.remove(self._server_socket) self._server_socket.close() + for handler in list(self._fd_to_handlers.values()): + handler.destroy() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index e3fe77c..a90df9f 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -290,3 +290,5 @@ class UDPRelay(object): self._eventloop.remove_periodic(self.handle_periodic) self._eventloop.remove(self._server_socket) self._server_socket.close() + for client in list(self._cache.values()): + client.close() From e08845d6f34ae6aecf1e5c2b0ca145e3414ae4bd Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 18:31:55 +0800 Subject: [PATCH 087/104] fix manager --- shadowsocks/manager.py | 2 +- shadowsocks/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 199a96d..96dcb8b 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -149,4 +149,4 @@ class Manager(object): def run(config): - Manager(config).run() \ No newline at end of file + Manager(config).run() diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 8ff13d6..3489bf5 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -49,7 +49,7 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - if config['manager_port']: + if config.get('manager_port', 0): logging.info('entering manager mode') manager.run(config) return From 9c3af61433b0a3827ffd8003f426e17d2dbd59fe Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 22:43:21 +0800 Subject: [PATCH 088/104] add statistics --- shadowsocks/manager.py | 33 ++++++++++++++++++++++++++++----- shadowsocks/tcprelay.py | 18 +++++++++++------- shadowsocks/udprelay.py | 8 ++++++-- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 96dcb8b..767c003 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -28,7 +28,8 @@ import collections from shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell -BUF_SIZE = 2048 +BUF_SIZE = 1506 +STAT_SEND_LIMIT = 100 class Manager(object): @@ -44,6 +45,7 @@ class Manager(object): self._statistics = collections.defaultdict(int) self._control_client_addr = None try: + # TODO use address instead of port self._control_socket.bind(('127.0.0.1', int(config['manager_port']))) self._control_socket.setblocking(False) @@ -53,6 +55,7 @@ class Manager(object): exit(1) self._loop.add(self._control_socket, eventloop.POLL_IN, self) + self._loop.add_periodic(self.handle_periodic) port_password = config['port_password'] del config['port_password'] @@ -70,8 +73,10 @@ class Manager(object): port)) return logging.info("adding server at %s:%d" % (config['server'], port)) - t = tcprelay.TCPRelay(config, self._dns_resolver, False) - u = udprelay.UDPRelay(config, self._dns_resolver, False) + t = tcprelay.TCPRelay(config, self._dns_resolver, False, + self.stat_callback) + u = udprelay.UDPRelay(config, self._dns_resolver, False, + self.stat_callback) t.add_to_loop(self._loop) u.add_to_loop(self._loop) self._relays[port] = (t, u) @@ -126,9 +131,27 @@ class Manager(object): logging.error(e) return None + def stat_callback(self, port, data_len): + self._statistics[port] += data_len + def handle_periodic(self): - # TODO send statistics - pass + r = {} + i = 0 + + def send_data(data_dict): + if data_dict: + data = common.to_bytes(json.dumps(data_dict, + separators=(',', ':'))) + self._send_control_data(b'stat: ' + data) + + for k, v in self._statistics.items(): + r[k] = v + i += 1 + if i >= STAT_SEND_LIMIT: + send_data(r) + r.clear() + send_data(r) + self._statistics.clear() def _send_control_data(self, data): if self._control_client_addr: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index e14dcaa..d11af31 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -147,10 +147,10 @@ class TCPRelayHandler(object): logging.debug('chosen server: %s:%d', server, server_port) return server, server_port - def _update_activity(self): + def _update_activity(self, data_len=0): # tell the TCP Relay we have activities recently # else it will think we are inactive and timed out - self._server.update_activity(self) + self._server.update_activity(self, data_len) def _update_stream(self, stream, status): # update a stream to a new waiting status @@ -317,7 +317,6 @@ class TCPRelayHandler(object): self._log_error(e) if self._config['verbose']: traceback.print_exc() - # TODO use logging when debug completed self.destroy() def _create_remote_socket(self, ip, port): @@ -388,7 +387,6 @@ class TCPRelayHandler(object): def _on_local_read(self): # handle all local read events and dispatch them to methods for # each stage - self._update_activity() if not self._local_sock: return is_local = self._is_local @@ -402,6 +400,7 @@ class TCPRelayHandler(object): if not data: self.destroy() return + self._update_activity(len(data)) if not is_local: data = self._encryptor.decrypt(data) if not data: @@ -424,10 +423,10 @@ class TCPRelayHandler(object): def _on_remote_read(self): # handle all remote read events - self._update_activity() data = None try: data = self._remote_sock.recv(BUF_SIZE) + except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): @@ -435,6 +434,7 @@ class TCPRelayHandler(object): if not data: self.destroy() return + self._update_activity(len(data)) if self._is_local: data = self._encryptor.decrypt(data) else: @@ -549,7 +549,7 @@ class TCPRelayHandler(object): class TCPRelay(object): - def __init__(self, config, dns_resolver, is_local): + def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config self._is_local = is_local self._dns_resolver = dns_resolver @@ -589,6 +589,7 @@ class TCPRelay(object): self._config['fast_open'] = False server_socket.listen(1024) self._server_socket = server_socket + self._stat_callback = stat_callback def add_to_loop(self, loop): if self._eventloop: @@ -607,7 +608,10 @@ class TCPRelay(object): self._timeouts[index] = None del self._handler_to_timeouts[hash(handler)] - def update_activity(self, handler): + def update_activity(self, handler, data_len): + if data_len and self._stat_callback: + self._stat_callback(self._listen_port, data_len) + # set handler to active now = int(time.time()) if now - handler.last_activity < eventloop.TIMEOUT_PRECISION: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index a90df9f..a36f981 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -81,7 +81,7 @@ def client_key(source_addr, server_af): class UDPRelay(object): - def __init__(self, config, dns_resolver, is_local): + def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config if is_local: self._listen_addr = config['local_address'] @@ -121,6 +121,7 @@ class UDPRelay(object): server_socket.bind((self._listen_addr, self._listen_port)) server_socket.setblocking(False) self._server_socket = server_socket + self._stat_callback = stat_callback def _get_a_server(self): server = self._config['server'] @@ -146,6 +147,8 @@ class UDPRelay(object): data, r_addr = server.recvfrom(BUF_SIZE) if not data: logging.debug('UDP handle_server: data is empty') + if self._stat_callback: + self._stat_callback(self._listen_port, len(data)) if self._is_local: frag = common.ord(data[2]) if frag != 0: @@ -181,7 +184,6 @@ class UDPRelay(object): af, socktype, proto, canonname, sa = addrs[0] key = client_key(r_addr, af) - logging.debug(key) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo @@ -221,6 +223,8 @@ class UDPRelay(object): if not data: logging.debug('UDP handle_client: data is empty') return + if self._stat_callback: + self._stat_callback(self._listen_port, len(data)) if not self._is_local: addrlen = len(r_addr[0]) if addrlen > 255: From d20a07192c28aea3cd37b8bc48ab5a14643f38f8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 23:11:56 +0800 Subject: [PATCH 089/104] allow manager to bind on unix socket --- shadowsocks/manager.py | 18 +++++++++++++----- shadowsocks/server.py | 2 +- shadowsocks/shell.py | 8 ++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 767c003..2da4f0d 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -40,18 +40,26 @@ class Manager(object): self._loop = eventloop.EventLoop() self._dns_resolver = asyncdns.DNSResolver() self._dns_resolver.add_to_loop(self._loop) - self._control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.IPPROTO_UDP) + self._statistics = collections.defaultdict(int) self._control_client_addr = None try: + manager_address = config['manager_address'] + if ':' in manager_address: + addr = manager_address.split(':') + addr = addr[0], int(addr[1]) + family = socket.AF_INET + else: + addr = manager_address + family = socket.AF_UNIX # TODO use address instead of port - self._control_socket.bind(('127.0.0.1', - int(config['manager_port']))) + self._control_socket = socket.socket(family, + socket.SOCK_DGRAM) + self._control_socket.bind(addr) self._control_socket.setblocking(False) except (OSError, IOError) as e: logging.error(e) - logging.error('can not bind to manager port') + logging.error('can not bind to manager address') exit(1) self._loop.add(self._control_socket, eventloop.POLL_IN, self) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 3489bf5..e25db4c 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -49,7 +49,7 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - if config.get('manager_port', 0): + if config.get('manager_address', 0): logging.info('entering manager mode') manager.run(config) return diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 586c2f6..bdba6c6 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -136,7 +136,7 @@ def get_config(is_local): else: shortopts = 'hd:s:p:k:m:c:t:vq' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user=', 'manager-port=', 'version'] + 'forbidden-ip=', 'user=', 'manager-address=', 'version'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -181,8 +181,8 @@ def get_config(is_local): config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) - elif key == '--manager-port': - config['manager_port'] = int(value) + elif key == '--manager-address': + config['manager_address'] = value elif key == '--user': config['user'] = to_str(value) elif key == '--forbidden-ip': @@ -319,7 +319,7 @@ Proxy options: --fast-open use TCP_FASTOPEN, requires Linux 3.7+ --workers WORKERS number of workers, available on Unix/Linux --forbidden-ip IPLIST comma seperated IP list forbidden to connect - --manager-port PORT optional server manager UDP port + --manager-address ADDR optional server manager UDP address, see wiki General options: -h, --help show this help message and exit From 4f948b22867b63ab9fccd55f3f351dea488e8001 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 23:59:14 +0800 Subject: [PATCH 090/104] fix password type in udprelay --- shadowsocks/udprelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index a36f981..96d1fbd 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -94,7 +94,7 @@ class UDPRelay(object): self._remote_addr = None self._remote_port = None self._dns_resolver = dns_resolver - self._password = config['password'] + self._password = common.to_bytes(config['password']) self._method = config['method'] self._timeout = config['timeout'] self._is_local = is_local From a8ae8ab373b215f30f1a54dc28e4e7eef18ee840 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 23:59:33 +0800 Subject: [PATCH 091/104] add unit test for manager --- shadowsocks/manager.py | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 2da4f0d..7093ad8 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -181,3 +181,91 @@ class Manager(object): def run(config): Manager(config).run() + + +def test(): + import time + import threading + import os + import struct + from shadowsocks import encrypt + + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + enc = [] + + def run_server(): + config = { + 'server': '127.0.0.1', + 'local_port': 1081, + 'port_password': { + '8381': 'foobar1', + '8382': 'foobar2' + }, + 'method': 'aes-256-cfb', + 'manager_address': '127.0.0.1:6001', + 'timeout': 60, + 'fast_open': False, + 'verbose': 2 + } + manager = Manager(config) + enc.append(manager) + manager.run() + + t = threading.Thread(target=run_server).start() + time.sleep(2) + manager = enc[0] + cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + cli.connect(('127.0.0.1', 6001)) + + # test add and remove + time.sleep(1) + cli.send(b'add: {"server_port":7001, "password":"1234"}') + time.sleep(1) + assert 7001 in manager._relays + cli.send(b'remove: {"server_port":8381}') + time.sleep(1) + assert 8381 not in manager._relays + logging.info('add and remove test passed') + + # test statistics for TCP + header = common.pack_addr(b'google.com') + struct.pack('>H', 80) + data = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 1, + header + b'GET /\r\n\r\n') + tcp_cli = socket.socket() + tcp_cli.connect(('127.0.0.1', 7001)) + tcp_cli.send(data) + rdata = tcp_cli.recv(4096) + tcp_cli.close() + rdata = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 0, rdata) + + data, addr = cli.recvfrom(1506) + data = common.to_str(data) + assert data.startswith('stat: ') + data = data.split('stat:')[1] + stats = json.loads(data) + assert '7001' in stats + logging.info('TCP statistics test passed') + + # test statistics for UDP + header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80) + data = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1, + header + b'test') + udp_cli = socket.socket(type=socket.SOCK_DGRAM) + udp_cli.sendto(data, ('127.0.0.1', 8382)) + tcp_cli.close() + + data, addr = cli.recvfrom(1506) + data = common.to_str(data) + assert data.startswith('stat: ') + data = data.split('stat:')[1] + stats = json.loads(data) + assert '8382' in stats + logging.info('UDP statistics test passed') + + os._exit(0) + + +if __name__ == '__main__': + test() From 30efc303607a44f8978e21c33060a528735714c1 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 00:34:20 +0800 Subject: [PATCH 092/104] fix pyflakes --- shadowsocks/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 7093ad8..7883b10 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -213,7 +213,7 @@ def test(): enc.append(manager) manager.run() - t = threading.Thread(target=run_server).start() + threading.Thread(target=run_server).start() time.sleep(2) manager = enc[0] cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) From 249bcc0b2910c6832ad1ae9b4964e362a915e223 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 00:52:25 +0800 Subject: [PATCH 093/104] refine unit test --- shadowsocks/manager.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 7883b10..75ecc08 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -186,14 +186,14 @@ def run(config): def test(): import time import threading - import os import struct from shadowsocks import encrypt - logging.basicConfig(level=logging.DEBUG, + logging.basicConfig(level=5, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') enc = [] + eventloop.TIMEOUT_PRECISION = 1 def run_server(): config = { @@ -213,8 +213,9 @@ def test(): enc.append(manager) manager.run() - threading.Thread(target=run_server).start() - time.sleep(2) + t = threading.Thread(target=run_server) + t.start() + time.sleep(1) manager = enc[0] cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cli.connect(('127.0.0.1', 6001)) @@ -236,9 +237,8 @@ def test(): tcp_cli = socket.socket() tcp_cli.connect(('127.0.0.1', 7001)) tcp_cli.send(data) - rdata = tcp_cli.recv(4096) + tcp_cli.recv(4096) tcp_cli.close() - rdata = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 0, rdata) data, addr = cli.recvfrom(1506) data = common.to_str(data) @@ -264,7 +264,8 @@ def test(): assert '8382' in stats logging.info('UDP statistics test passed') - os._exit(0) + manager._loop.stop() + t.join() if __name__ == '__main__': From 5ed73c15f0624ec67feb4ee1c0aeceae3e4d20b0 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 01:04:03 +0800 Subject: [PATCH 094/104] bump 2.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4a6e9f3..0a438d4 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.7.1", + version="2.8", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 583b54f4260cca431dc1a7b32231699125aac9c6 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 14:58:40 +0800 Subject: [PATCH 095/104] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 96840f3..ff8516b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.8 2015-08-06 +- Add Shadowsocks manager + 2.7 2015-08-02 - Optimize speed for multiple ports From 3576b3006c2737fa24b459992f6bb156e06791fc Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 15:13:29 +0800 Subject: [PATCH 096/104] support IPv6 on control socket --- shadowsocks/manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 75ecc08..8639fda 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -46,13 +46,17 @@ class Manager(object): try: manager_address = config['manager_address'] if ':' in manager_address: - addr = manager_address.split(':') + addr = manager_address.rsplit(':', 1) addr = addr[0], int(addr[1]) - family = socket.AF_INET + addrs = socket.getaddrinfo(addr[0], addr[1]) + if addrs: + family = addrs[0][0] + else: + logging.error('invalid address: %s', manager_address) + exit(1) else: addr = manager_address family = socket.AF_UNIX - # TODO use address instead of port self._control_socket = socket.socket(family, socket.SOCK_DGRAM) self._control_socket.bind(addr) From 77f9979b2e400784cd7d2978acc0fc9ded5424c1 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 17:24:51 +0800 Subject: [PATCH 097/104] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbdb9c1..5d94e0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,8 @@ How to Contribute ================= +Notice this is the repository for Shadowsocks Python version. If you have problems with Android / iOS / Windows etc clients, please post your questions in their issue trackers. + Pull Requests ------------- From c8b3f71e1b106024dde1f73281519c5ef1e5edcc Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 19:44:59 +0800 Subject: [PATCH 098/104] respond ok to add and remove commands --- shadowsocks/manager.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 8639fda..d93eacc 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -114,14 +114,17 @@ class Manager(object): command, config = parsed a_config = self._config.copy() if config: + # let the command override the configuration file a_config.update(config) if 'server_port' not in a_config: logging.error('can not find server_port in config') else: if command == 'add': self.add_port(a_config) + self._send_control_data(b'ok') elif command == 'remove': self.remove_port(a_config) + self._send_control_data(b'ok') elif command == 'ping': self._send_control_data(b'pong') else: @@ -152,6 +155,7 @@ class Manager(object): def send_data(data_dict): if data_dict: + # use compact JSON format (without space) data = common.to_bytes(json.dumps(data_dict, separators=(',', ':'))) self._send_control_data(b'stat: ' + data) @@ -159,6 +163,7 @@ class Manager(object): for k, v in self._statistics.items(): r[k] = v i += 1 + # split the data into segments that fit in UDP packets if i >= STAT_SEND_LIMIT: send_data(r) r.clear() @@ -229,9 +234,14 @@ def test(): cli.send(b'add: {"server_port":7001, "password":"1234"}') time.sleep(1) assert 7001 in manager._relays + data, addr = cli.recvfrom(1506) + assert b'ok' in data + cli.send(b'remove: {"server_port":8381}') time.sleep(1) assert 8381 not in manager._relays + data, addr = cli.recvfrom(1506) + assert b'ok' in data logging.info('add and remove test passed') # test statistics for TCP From f04e26888596f0d9bbe909c2cd20d761f57e7b39 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 19:47:33 +0800 Subject: [PATCH 099/104] bump 2.8.1 --- CHANGES | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ff8516b..6795244 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.8.1 2015-08-06 +- Respond ok to add and remove commands + 2.8 2015-08-06 - Add Shadowsocks manager diff --git a/setup.py b/setup.py index 0a438d4..e5e7193 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.8", + version="2.8.1", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From c5dd081216ee83ef6a106157694cdc125c235718 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 9 Aug 2015 01:33:27 +0800 Subject: [PATCH 100/104] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 76d759a..7cda7e7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ shadowsocks A fast tunnel proxy that helps you bypass firewalls. +Features: +- TCP & UDP support +- User management API +- TCP Fast Open +- Workers and graceful restart +- Destination IP blacklist + Server ------ From 3c1154923fe1bf1e0c1521cd38d1681a6a39e98d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 10 Aug 2015 00:05:22 +0800 Subject: [PATCH 101/104] fix json unicode issue in manager --- shadowsocks/manager.py | 2 +- shadowsocks/shell.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index d93eacc..e125ef4 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -258,7 +258,7 @@ def test(): data = common.to_str(data) assert data.startswith('stat: ') data = data.split('stat:')[1] - stats = json.loads(data) + stats = shell.parse_json_in_str(data) assert '7001' in stats logging.info('TCP statistics test passed') diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index bdba6c6..c91fc22 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -148,8 +148,7 @@ def get_config(is_local): logging.info('loading config from %s' % config_path) with open(config_path, 'rb') as f: try: - config = json.loads(f.read().decode('utf8'), - object_hook=_decode_dict) + config = parse_json_in_str(f.read().decode('utf8')) except ValueError as e: logging.error('found an error in config.json: %s', e.message) @@ -359,3 +358,8 @@ def _decode_dict(data): value = _decode_dict(value) rv[key] = value return rv + + +def parse_json_in_str(data): + # parse json and convert everything from unicode to str + return json.loads(data, object_hook=_decode_dict) From a434eef096fe6cd339a205b85205b6ec8e443ad0 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 10 Aug 2015 11:53:54 +0800 Subject: [PATCH 102/104] fix json decode issue --- shadowsocks/manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index e125ef4..e8009b4 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -140,7 +140,7 @@ class Manager(object): return data, None command, config_json = parts try: - config = json.loads(config_json) + config = shell.parse_json_in_str(config_json) return command, config except Exception as e: logging.error(e) @@ -231,7 +231,7 @@ def test(): # test add and remove time.sleep(1) - cli.send(b'add: {"server_port":7001, "password":"1234"}') + cli.send(b'add: {"server_port":7001, "password":"asdfadsfasdf"}') time.sleep(1) assert 7001 in manager._relays data, addr = cli.recvfrom(1506) @@ -246,7 +246,7 @@ def test(): # test statistics for TCP header = common.pack_addr(b'google.com') + struct.pack('>H', 80) - data = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 1, + data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1, header + b'GET /\r\n\r\n') tcp_cli = socket.socket() tcp_cli.connect(('127.0.0.1', 7001)) From 7c08101ce8a673fafb22477e8ad720aa57114a1f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 10 Aug 2015 12:37:42 +0800 Subject: [PATCH 103/104] update CHANGES --- CHANGES | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6795244..d6fe932 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.8.2 2015-08-10 +- Fix a encoding problem in manager + 2.8.1 2015-08-06 - Respond ok to add and remove commands diff --git a/setup.py b/setup.py index e5e7193..e6a7ff7 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.8.1", + version="2.8.2", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From a2bc6e19457f51a421b7d2866be5903e0d71fd2f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 11 Aug 2015 17:55:16 +0800 Subject: [PATCH 104/104] fix #414 --- shadowsocks/server.py | 13 +++++++------ shadowsocks/shell.py | 7 ++++--- tests/server-multi-passwd-empty.json | 8 ++++++++ 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 tests/server-multi-passwd-empty.json diff --git a/shadowsocks/server.py b/shadowsocks/server.py index e25db4c..d4cf961 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -42,12 +42,13 @@ def main(): 'will be ignored') else: config['port_password'] = {} - server_port = config['server_port'] - if type(server_port) == list: - for a_server_port in server_port: - config['port_password'][a_server_port] = config['password'] - else: - config['port_password'][str(server_port)] = config['password'] + server_port = config.get('server_port', None) + if server_port: + if type(server_port) == list: + for a_server_port in server_port: + config['port_password'][a_server_port] = config['password'] + else: + config['port_password'][str(server_port)] = config['password'] if config.get('manager_address', 0): logging.info('entering manager mode') diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index c91fc22..a78e18f 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -84,7 +84,8 @@ def check_config(config, is_local): sys.exit(2) if not is_local and not config.get('password', None) \ - and not config.get('port_password', None): + and not config.get('port_password', None) \ + and not config.get('manager_address'): logging.error('password or port_password not specified') print_help(is_local) sys.exit(2) @@ -92,7 +93,7 @@ def check_config(config, is_local): if 'local_port' in config: config['local_port'] = int(config['local_port']) - if 'server_port' in config and type(config['server_port']) != list: + if config.get('server_port', None) and type(config['server_port']) != list: config['server_port'] = int(config['server_port']) if config.get('local_address', '') in [b'0.0.0.0']: @@ -240,7 +241,7 @@ def get_config(is_local): except Exception as e: logging.error(e) sys.exit(2) - config['server_port'] = config.get('server_port', 8388) + config['server_port'] = config.get('server_port', None) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') diff --git a/tests/server-multi-passwd-empty.json b/tests/server-multi-passwd-empty.json new file mode 100644 index 0000000..de354e5 --- /dev/null +++ b/tests/server-multi-passwd-empty.json @@ -0,0 +1,8 @@ +{ + "server": "127.0.0.1", + "local_port": 1081, + "port_password": { + }, + "timeout": 60, + "method": "aes-256-cfb" +}