From 5e476843ec8fc708033170a68a9c3bc425ecfb95 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 12 Jan 2015 23:19:24 +0800 Subject: [PATCH 001/196] fix python3 --- tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index d213780..933027b 100755 --- a/tests/test.py +++ b/tests/test.py @@ -90,7 +90,6 @@ try: for fd in r: line = fd.readline() - sys.stderr.write(line) if not line: if stage == 2 and fd == p3.stdout: stage = 3 @@ -98,6 +97,7 @@ try: stage = 5 if bytes != str: line = str(line, 'utf8') + sys.stderr.write(line) if line.find('starting local') >= 0: local_ready = True if line.find('starting server') >= 0: From 53a7e4d0e4a8909a9825ad956a0a4c7265561dad Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 12 Jan 2015 23:21:16 +0800 Subject: [PATCH 002/196] remove print --- shadowsocks/udprelay.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 1bdd153..ff6391c 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -112,7 +112,6 @@ class UDPRelay(object): self._closed = False self._last_time = time.time() self._sockets = set() - print(config) if 'forbidden_ip' in config: self._forbidden_iplist = config['forbidden_ip'] else: From 5c05a74727ee22a06859c5f50ed0af88ad65cdd4 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 13 Jan 2015 00:42:27 +0800 Subject: [PATCH 003/196] fix a potential name conflict --- shadowsocks/crypto/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index c8cbab5..5425908 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -27,7 +27,7 @@ import logging def find_library(possible_lib_names, search_symbol, library_name): - from ctypes.util import find_library + import ctypes.util from ctypes import CDLL paths = [] @@ -36,7 +36,7 @@ def find_library(possible_lib_names, search_symbol, library_name): possible_lib_names = [possible_lib_names] for name in possible_lib_names: - path = find_library(name) + path = ctypes.util.find_library(name) if path: paths.append(path) From af6c6f3f238fe866e0d9a0b21f49d0edfd97852b Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 14 Jan 2015 12:59:43 +0800 Subject: [PATCH 004/196] also search lib* for library names --- shadowsocks/crypto/util.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index 5425908..6d7d222 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -35,7 +35,12 @@ def find_library(possible_lib_names, search_symbol, library_name): if type(possible_lib_names) not in (list, tuple): possible_lib_names = [possible_lib_names] - for name in possible_lib_names: + lib_names = [] + for lib_name in possible_lib_names: + lib_names.append(lib_name) + lib_names.append('lib' + lib_name) + + for name in lib_names: path = ctypes.util.find_library(name) if path: paths.append(path) @@ -46,7 +51,7 @@ def find_library(possible_lib_names, search_symbol, library_name): # tools underlying find_library on linux. import glob - for name in possible_lib_names: + for name in lib_names: patterns = [ '/usr/local/lib*/lib%s.*' % name, '/usr/lib*/lib%s.*' % name, @@ -106,10 +111,11 @@ def test_find_library(): assert find_library('c', 'strcpy', 'libc') is not None assert find_library(['c'], 'strcpy', 'libc') is not None assert find_library(('c',), 'strcpy', 'libc') is not None - assert find_library('crypto', 'EVP_CipherUpdate', 'libcrypto') is not None + assert find_library(('crypto', 'eay32'), 'EVP_CipherUpdate', + 'libcrypto') is not None assert find_library('notexist', 'strcpy', 'libnotexist') is None assert find_library('c', 'symbol_not_exist', 'c') is None - assert find_library(('notexist', 'c', 'crypto'), + assert find_library(('notexist', 'c', 'crypto', 'eay32'), 'EVP_CipherUpdate', 'libc') is not None From 5179e018e2f14b377ca1c8b95f4c42bfb0c3b9c1 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 14 Jan 2015 13:12:12 +0800 Subject: [PATCH 005/196] bump --- CHANGES | 5 ++++- setup.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index b5c9062..27c8562 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,7 @@ -2.6.3 2015-01-03 +2.6.4 2015-01-14 +- Also search lib* when searching libraries + +2.6.3 2015-01-12 - Support --forbidden-ip to ban some IP, i.e. localhost - Search OpenSSL and libsodium harder - Now works on OpenWRT diff --git a/setup.py b/setup.py index 725ec71..c287c5c 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.3", + version="2.6.4", license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From bd22e3ef753f0530a0124d15be6d29f13681ccf1 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 16 Jan 2015 16:50:18 +0800 Subject: [PATCH 006/196] try every dll that matches by name in PATH --- shadowsocks/crypto/util.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index 6d7d222..1242f8e 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -23,9 +23,28 @@ from __future__ import absolute_import, division, print_function, \ with_statement +import os import logging +def find_library_nt(name): + # modified from ctypes.util + # ctypes.util.find_library just returns first result he found + # but we want to try them all + # because on Windows, users may have both 32bit and 64bit version installed + results = [] + for directory in os.environ['PATH'].split(os.pathsep): + fname = os.path.join(directory, name) + if os.path.isfile(fname): + results.append(fname) + if fname.lower().endswith(".dll"): + continue + fname = fname + ".dll" + if os.path.isfile(fname): + results.append(fname) + return results + + def find_library(possible_lib_names, search_symbol, library_name): import ctypes.util from ctypes import CDLL @@ -41,9 +60,12 @@ def find_library(possible_lib_names, search_symbol, library_name): lib_names.append('lib' + lib_name) for name in lib_names: - path = ctypes.util.find_library(name) - if path: - paths.append(path) + if os.name == "nt": + paths.extend(find_library_nt(name)) + else: + path = ctypes.util.find_library(name) + if path: + paths.append(path) if not paths: # We may get here when find_library fails because, for example, @@ -56,8 +78,7 @@ def find_library(possible_lib_names, search_symbol, library_name): '/usr/local/lib*/lib%s.*' % name, '/usr/lib*/lib%s.*' % name, 'lib%s.*' % name, - '%s.dll' % name, - 'lib%s.dll' % name] + '%s.dll' % name] for pat in patterns: files = glob.glob(pat) From 13413267dcc8dc16f667fcf4996c93a8fec7aa87 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 16 Jan 2015 17:06:48 +0800 Subject: [PATCH 007/196] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b351ba..0e45c91 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,17 @@ Server ### Install -#### Debian / Ubuntu: +Debian / Ubuntu: apt-get install python-pip pip install shadowsocks -#### CentOS: +CentOS: yum install python-setuptools && easy_install pip pip install shadowsocks -#### Windows: +Windows: See [Install Server on Windows] From 6efb3d00e414a69c77f018dab1c853d808a7c4e4 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 16 Jan 2015 18:52:01 +0800 Subject: [PATCH 008/196] fix #264 --- shadowsocks/asyncdns.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 18222a6..6f60dc9 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -93,11 +93,12 @@ def build_address(address): return b''.join(results) -def build_request(address, qtype, request_id): - header = struct.pack('!HBBHHHH', request_id, 1, 0, 1, 0, 0, 0) +def build_request(address, qtype): + request_id = os.urandom(2) + header = struct.pack('!BBHHHH', 1, 0, 1, 0, 0, 0) addr = build_address(address) qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN) - return header + addr + qtype_qclass + return request_id + header + addr + qtype_qclass def parse_ip(addrtype, data, length, offset): @@ -270,7 +271,6 @@ class DNSResolver(object): def __init__(self): self._loop = None - self._request_id = 1 self._hosts = {} self._hostname_status = {} self._hostname_to_cb = {} @@ -412,10 +412,7 @@ class DNSResolver(object): del self._hostname_status[hostname] def _send_req(self, hostname, qtype): - self._request_id += 1 - if self._request_id > 32768: - self._request_id = 1 - req = build_request(hostname, qtype, self._request_id) + req = build_request(hostname, qtype) for server in self._servers: logging.debug('resolving %s with type %d using server %s', hostname, qtype, server) From 2e9ce11ea15c303b769c2ab5e327c7372cfef2c7 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 18 Jan 2015 18:32:04 +0800 Subject: [PATCH 009/196] 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 010/196] 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 011/196] 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 012/196] 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 013/196] 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 014/196] 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 015/196] 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 016/196] 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 017/196] 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 018/196] 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 019/196] 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 020/196] 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 021/196] 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 022/196] 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 023/196] 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 024/196] 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 025/196] 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 026/196] 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 027/196] 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 028/196] 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 029/196] 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 030/196] 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 031/196] 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 032/196] 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 033/196] 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 034/196] 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 035/196] 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 036/196] 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 037/196] 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 038/196] 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 039/196] 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 040/196] 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 041/196] 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 042/196] 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 043/196] 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 044/196] 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 045/196] 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 046/196] 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 047/196] 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 048/196] 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 049/196] 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 050/196] 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 051/196] 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 052/196] 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 053/196] 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 054/196] 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 055/196] 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 056/196] 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 057/196] 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 058/196] 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 059/196] 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 060/196] 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 061/196] 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 062/196] 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 063/196] 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 064/196] 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 065/196] 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 066/196] 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 067/196] 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 068/196] 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 069/196] 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 070/196] 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 071/196] 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 072/196] 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 073/196] 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 074/196] 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 075/196] 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 076/196] 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 077/196] 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 078/196] 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 079/196] 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 080/196] 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 081/196] 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 082/196] 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 083/196] 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 084/196] 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 085/196] 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 086/196] 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 087/196] 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 088/196] 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 089/196] 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 090/196] 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 091/196] 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 092/196] 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 093/196] 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 094/196] 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 095/196] 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 096/196] 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 097/196] 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 098/196] 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 099/196] 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 100/196] 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 101/196] 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 102/196] 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 103/196] 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 104/196] 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 105/196] 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 106/196] 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 107/196] 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 108/196] 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 109/196] 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 110/196] 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 111/196] 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 112/196] 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" +} From f19d0ea6fdc4dba41fb3a0cc2454e1888efa984b Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 15 Aug 2015 22:36:29 +0800 Subject: [PATCH 113/196] fix #425 --- shadowsocks/daemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index 8dc5608..77ed323 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -117,7 +117,7 @@ def daemon_start(pid_file, log_file): sys.exit(1) os.setsid() - signal.signal(signal.SIG_IGN, signal.SIGHUP) + signal.signal(signal.SIGHUP, signal.SIG_IGN) print('started') os.kill(ppid, signal.SIGTERM) From eb033a8f6af6d442c8b63a87d4fb0013dc4888b5 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 16 Aug 2015 19:53:31 +0800 Subject: [PATCH 114/196] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cda7e7..232eba6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ shadowsocks [![Build Status]][Travis CI] [![Coverage Status]][Coverage] -A fast tunnel proxy that helps you bypass firewalls. +A fast tunnel proxy that helps you [bypass firewalls]. Features: - TCP & UDP support @@ -95,6 +95,7 @@ Bugs and Issues [Android]: https://github.com/shadowsocks/shadowsocks-android [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat +[bypass firewalls]: https://github.com/shadowsocks/shadowsocks/wiki/Objective [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/linux/htmlcov/index.html From 0c4f79284ea9518baa029eb00c65bc37ec7ab4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=9E=97=E5=93=B2?= Date: Mon, 11 May 2015 09:32:40 +0800 Subject: [PATCH 115/196] allow user to override system dnsserver with config. config item: dns_server list eg: dns_server:[8.8.8.8,8.8.4.4] --- shadowsocks/asyncdns.py | 9 ++++++--- shadowsocks/server.py | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index c5fc99d..25a0e6b 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -248,7 +248,7 @@ STATUS_IPV6 = 1 class DNSResolver(object): - def __init__(self): + def __init__(self, server_list=None): self._loop = None self._hosts = {} self._hostname_status = {} @@ -256,8 +256,11 @@ class DNSResolver(object): self._cb_to_hostname = {} self._cache = lru_cache.LRUCache(timeout=300) self._sock = None - self._servers = None - self._parse_resolv() + if server_list is None: + self._servers = None + self._parse_resolv() + else: + self._servers = server_list self._parse_hosts() # TODO monitor hosts change and reload hosts # TODO parse /etc/gai.conf and follow its rules diff --git a/shadowsocks/server.py b/shadowsocks/server.py index d4cf961..5896c7d 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -57,7 +57,12 @@ def main(): tcp_servers = [] udp_servers = [] - dns_resolver = asyncdns.DNSResolver() + + if 'dns_server' in config: # allow override settings in resolv.conf + dns_resolver = asyncdns.DNSResolver(config['dns_server']) + else: + dns_resolver = asyncdns.DNSResolver() + port_password = config['port_password'] del config['port_password'] for port, password in port_password.items(): From ffed9b2cae3265842b4be5aa8058e5f638ffc4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=9E=97=E5=93=B2?= Date: Mon, 17 Aug 2015 10:14:23 +0800 Subject: [PATCH 116/196] dns_server config test --- tests/jenkins.sh | 1 + tests/server-dnsserver.json | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/server-dnsserver.json diff --git a/tests/jenkins.sh b/tests/jenkins.sh index a85c461..5b53e93 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -42,6 +42,7 @@ 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-dnsserver.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/tests/server-dnsserver.json b/tests/server-dnsserver.json new file mode 100644 index 0000000..5d55cdc --- /dev/null +++ b/tests/server-dnsserver.json @@ -0,0 +1,11 @@ +{ + "server":"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, + "dns_server": ["8.8.8.8","8.8.4.4"] +} From 61a0b2d1ac81ab93734f6cef5494186b8abb4bd9 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 17 Aug 2015 20:36:34 +0800 Subject: [PATCH 117/196] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 232eba6..372896a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ shadowsocks [![Build Status]][Travis CI] [![Coverage Status]][Coverage] +[中文说明] + A fast tunnel proxy that helps you [bypass firewalls]. Features: @@ -112,3 +114,4 @@ Bugs and Issues [Troubleshooting]: https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting [Wiki]: https://github.com/shadowsocks/shadowsocks/wiki [Windows]: https://github.com/shadowsocks/shadowsocks-csharp +[中文说明]: https://github.com/shadowsocks/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E From 5b450acfaa15cd6c2d3e8ab99f9297542df74025 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 20 Aug 2015 18:52:42 +0800 Subject: [PATCH 118/196] Update README.md --- README.md | 62 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 372896a..b61c0db 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,7 @@ shadowsocks =========== -[![PyPI version]][PyPI] -[![Build Status]][Travis CI] -[![Coverage Status]][Coverage] - -[中文说明] - -A fast tunnel proxy that helps you [bypass firewalls]. +A fast tunnel proxy that helps you bypass firewalls. Features: - TCP & UDP support @@ -54,16 +48,6 @@ To check the log: Check all the options via `-h`. You can also use a [Configuration] file instead. -Client ------- - -* [Windows] / [OS X] -* [Android] / [iOS] -* [OpenWRT] - -Use GUI clients on your local PC/phones. Check the README of your client -for more information. - Documentation ------------- @@ -72,46 +56,4 @@ You can find all the documentation in the [Wiki]. License ------- -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 ----------------- - -* [Troubleshooting] -* [Issue Tracker] -* [Mailing list] - - - -[Android]: https://github.com/shadowsocks/shadowsocks-android -[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat -[bypass firewalls]: https://github.com/shadowsocks/shadowsocks/wiki/Objective -[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/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 -[Install Server on Windows]: https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows -[Mailing list]: https://groups.google.com/group/shadowsocks -[OpenWRT]: https://github.com/shadowsocks/openwrt-shadowsocks -[OS X]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help -[PyPI]: https://pypi.python.org/pypi/shadowsocks -[PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat -[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-csharp -[中文说明]: https://github.com/shadowsocks/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E +Apache License From 6683dfd3bb61fac52f77d35055c3956bb8f7c43f Mon Sep 17 00:00:00 2001 From: mengskysama Date: Fri, 9 Oct 2015 14:49:09 +0800 Subject: [PATCH 119/196] fix manger bug --- shadowsocks/manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index e8009b4..b42ffa9 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -29,7 +29,7 @@ from shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell BUF_SIZE = 1506 -STAT_SEND_LIMIT = 100 +STAT_SEND_LIMIT = 50 class Manager(object): @@ -167,7 +167,9 @@ class Manager(object): if i >= STAT_SEND_LIMIT: send_data(r) r.clear() - send_data(r) + i = 0 + if len(r) > 0: + send_data(r) self._statistics.clear() def _send_control_data(self, data): From 4d24b7477a9b969d9b3f13c8e55fb586cdbf2e7a Mon Sep 17 00:00:00 2001 From: mengskysama Date: Mon, 26 Oct 2015 09:43:48 +0800 Subject: [PATCH 120/196] fix bug! if _last_visits[index] len > 1 --- shadowsocks/lru_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/lru_cache.py b/shadowsocks/lru_cache.py index 401f19b..ff4fc7d 100644 --- a/shadowsocks/lru_cache.py +++ b/shadowsocks/lru_cache.py @@ -87,8 +87,8 @@ class LRUCache(collections.MutableMapping): if value not in self._closed_values: self.close_callback(value) self._closed_values.add(value) + self._last_visits.popleft() for key in self._time_to_keys[least]: - self._last_visits.popleft() if key in self._store: if now - self._keys_to_last_time[key] > self.timeout: del self._store[key] From 0aba0aa40c0a8249d5b7f9488ee7cf9314a05554 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Mon, 26 Oct 2015 09:45:02 +0800 Subject: [PATCH 121/196] fix memory leak! _dns_cache bommmm --- shadowsocks/udprelay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 96d1fbd..ab12c54 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -285,6 +285,7 @@ class UDPRelay(object): logging.info('closed UDP port %d', self._listen_port) self._cache.sweep() self._client_fd_to_server_addr.sweep() + self._dns_cache.sweep() def close(self, next_tick=False): logging.debug('UDP close') From 34851fbcb312d7d44b9b6ed0ad6f409c3abe5c7c Mon Sep 17 00:00:00 2001 From: mengskysama Date: Mon, 26 Oct 2015 10:48:52 +0800 Subject: [PATCH 122/196] readme --- README.md | 6 ++++++ README.rst | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 7cda7e7..007df02 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +About shadowsocks-rm +---------------- + +This project is https://github.com/shadowsocks/shadowsocks clone. I JUST fix bug on the original code. Unless it is necessary to have additional features. + + shadowsocks =========== diff --git a/README.rst b/README.rst index bf2a3ec..fe6fd51 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,8 @@ +About shadowsocks-rm +--------------- + +This project is https://github.com/shadowsocks/shadowsocks clone. I JUST fix bug on the original code. Unless it is necessary to have additional features. + shadowsocks =========== From 173323c0f867d2e7e821966536a13879befa3ca9 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Fri, 13 Nov 2015 14:29:27 +0800 Subject: [PATCH 123/196] Update common.py https://github.com/mengskysama/shadowsocks-rm/issues/4 --- shadowsocks/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index db4beea..857bde6 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -154,7 +154,7 @@ def parse_header(data): elif addrtype == ADDRTYPE_HOST: if len(data) > 2: addrlen = ord(data[1]) - if len(data) >= 2 + addrlen: + if len(data) >= 4 + addrlen: dest_addr = data[2:2 + addrlen] dest_port = struct.unpack('>H', data[2 + addrlen:4 + addrlen])[0] From 767b9217f874db37e947f5883f06e11e64ba9861 Mon Sep 17 00:00:00 2001 From: jsy Date: Sun, 6 Dec 2015 02:02:25 +0800 Subject: [PATCH 124/196] one time auth server side --- shadowsocks/common.py | 29 +++++++++++--- shadowsocks/encrypt.py | 15 +++++--- shadowsocks/shell.py | 3 ++ shadowsocks/tcprelay.py | 83 +++++++++++++++++++++++++++++++++++++---- shadowsocks/udprelay.py | 21 ++++++++++- 5 files changed, 130 insertions(+), 21 deletions(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 857bde6..7ec04ba 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -21,6 +21,21 @@ from __future__ import absolute_import, division, print_function, \ import socket import struct import logging +import hashlib +import hmac + + +ONETIMEAUTH_BYTES = 10 +ONETIMEAUTH_CHUNK_BYTES = 12 +ONETIMEAUTH_CHUNK_DATA_LEN = 2 + + +def sha1_hmac(secret, data): + return hmac.new(secret, data, hashlib.sha1).digest() + + +def onetimeauth_verify(_hash, data, key): + return _hash == sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] def compat_ord(s): @@ -118,9 +133,11 @@ def patch_socket(): patch_socket() -ADDRTYPE_IPV4 = 1 -ADDRTYPE_IPV6 = 4 -ADDRTYPE_HOST = 3 +ADDRTYPE_IPV4 = 0x01 +ADDRTYPE_IPV6 = 0x04 +ADDRTYPE_HOST = 0x03 +ADDRTYPE_AUTH = 0x10 +ADDRTYPE_MASK = 0xF def pack_addr(address): @@ -144,14 +161,14 @@ def parse_header(data): dest_addr = None dest_port = None header_length = 0 - if addrtype == ADDRTYPE_IPV4: + if addrtype & ADDRTYPE_MASK == ADDRTYPE_IPV4: if len(data) >= 7: dest_addr = socket.inet_ntoa(data[1:5]) dest_port = struct.unpack('>H', data[5:7])[0] header_length = 7 else: logging.warn('header is too short') - elif addrtype == ADDRTYPE_HOST: + elif addrtype & ADDRTYPE_MASK == ADDRTYPE_HOST: if len(data) > 2: addrlen = ord(data[1]) if len(data) >= 4 + addrlen: @@ -163,7 +180,7 @@ def parse_header(data): logging.warn('header is too short') else: logging.warn('header is too short') - elif addrtype == ADDRTYPE_IPV6: + elif addrtype & ADDRTYPE_MASK == ADDRTYPE_IPV6: if len(data) >= 19: dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17]) dest_port = struct.unpack('>H', data[17:19])[0] diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index 4e87f41..54dbb42 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -69,17 +69,18 @@ def EVP_BytesToKey(password, key_len, iv_len): class Encryptor(object): - def __init__(self, key, method): - self.key = key + def __init__(self, password, method): + self.password = password + self.key = None self.method = method - self.iv = None self.iv_sent = False self.cipher_iv = b'' self.decipher = None + self.decipher_iv = None method = method.lower() self._method_info = self.get_method_info(method) if self._method_info: - self.cipher = self.get_cipher(key, method, 1, + self.cipher = self.get_cipher(password, method, 1, random_string(self._method_info[1])) else: logging.error('method %s not supported' % method) @@ -101,7 +102,7 @@ class Encryptor(object): else: # key_length == 0 indicates we should use the key directly key, iv = password, b'' - + self.key = key iv = iv[:m[1]] if op == 1: # this iv is for cipher not decipher @@ -123,7 +124,8 @@ class Encryptor(object): if self.decipher is None: decipher_iv_len = self._method_info[1] decipher_iv = buf[:decipher_iv_len] - self.decipher = self.get_cipher(self.key, self.method, 0, + self.decipher_iv = decipher_iv + self.decipher = self.get_cipher(self.password, self.method, 0, iv=decipher_iv) buf = buf[decipher_iv_len:] if len(buf) == 0: @@ -135,6 +137,7 @@ def encrypt_all(password, method, op, data): result = [] method = method.lower() (key_len, iv_len, m) = method_supported[method] + key = None if key_len > 0: key, _ = EVP_BytesToKey(password, key_len, iv_len) else: diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index c91fc22..e2284da 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -174,6 +174,8 @@ def get_config(is_local): v_count += 1 # '-vv' turns on more verbose mode config['verbose'] = v_count + elif key == '-a': + config['one_time_auth'] = True elif key == '-t': config['timeout'] = int(value) elif key == '--fast-open': @@ -315,6 +317,7 @@ Proxy options: -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb -t TIMEOUT timeout in seconds, default: 300 + -a ONE_TIME_AUTH one time auth --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 diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index d11af31..22ba104 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -27,7 +27,8 @@ import traceback import random from shadowsocks import encrypt, eventloop, shell, common -from shadowsocks.common import parse_header +from shadowsocks.common import parse_header, onetimeauth_verify, \ + ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time TIMEOUTS_CLEAN_SIZE = 512 @@ -107,6 +108,14 @@ class TCPRelayHandler(object): self._stage = STAGE_INIT self._encryptor = encrypt.Encryptor(config['password'], config['method']) + if 'one_time_auth' in config and config['one_time_auth']: + self._one_time_auth_enable = True + else: + self._one_time_auth_enable = False + self._one_time_auth_buff_head = '' + self._one_time_auth_buff_data = '' + self._one_time_auth_len = 0 + self._one_time_auth_chunk_idx = 0 self._fastopen_connected = False self._data_to_write_to_local = [] self._data_to_write_to_remote = [] @@ -225,7 +234,11 @@ class TCPRelayHandler(object): def _handle_stage_connecting(self, data): if self._is_local: data = self._encryptor.encrypt(data) - self._data_to_write_to_remote.append(data) + if self._one_time_auth_enable: + self._one_time_auth_chunk_data(data, + self._data_to_write_to_remote.append) + else: + self._data_to_write_to_remote.append(data) if self._is_local and not self._fastopen_connected and \ self._config['fast_open']: # for sslocal and fastopen, we basically wait for data and use @@ -293,6 +306,17 @@ 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])) + # spec https://shadowsocks.org/en/spec/one-time-auth.html + if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: + if len(data) < header_length + ONETIMEAUTH_BYTES: + logging.warn('one time auth header is too short') + return None + if onetimeauth_verify(data[header_length: header_length+ONETIMEAUTH_BYTES], + data[:header_length], + self._encryptor.decipher_iv + self._encryptor.key) is False: + logging.warn('one time auth fail') + self.destroy() + header_length += ONETIMEAUTH_BYTES self._remote_address = (common.to_str(remote_addr), remote_port) # pause reading self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) @@ -308,7 +332,11 @@ class TCPRelayHandler(object): self._dns_resolver.resolve(self._chosen_server[0], self._handle_dns_resolved) else: - if len(data) > header_length: + if self._one_time_auth_enable: + data = data[header_length:] + self._one_time_auth_chunk_data(data, + self._data_to_write_to_remote.append) + elif len(data) > header_length: self._data_to_write_to_remote.append(data[header_length:]) # notice here may go into _handle_dns_resolved directly self._dns_resolver.resolve(remote_addr, @@ -344,7 +372,6 @@ class TCPRelayHandler(object): if result: ip = result[1] if ip: - try: self._stage = STAGE_CONNECTING remote_addr = ip @@ -384,6 +411,50 @@ class TCPRelayHandler(object): traceback.print_exc() self.destroy() + def _write_to_sock_remote(self, data): + self._write_to_sock(data, self._remote_sock) + + def _one_time_auth_chunk_data(self, data, data_cb): + # spec https://shadowsocks.org/en/spec/one-time-auth.html + while len(data) > 0: + if self._one_time_auth_len == 0: + # get DATA.LEN + HMAC-SHA1 + length = ONETIMEAUTH_CHUNK_BYTES - len(self._one_time_auth_buff_head) + self._one_time_auth_buff_head += data[:length] + data = data[length:] + if len(self._one_time_auth_buff_head) < ONETIMEAUTH_CHUNK_BYTES: + # wait more data + return + self._one_time_auth_len = struct.unpack('>H', + self._one_time_auth_buff_head[:ONETIMEAUTH_CHUNK_DATA_LEN])[0] + length = min(self._one_time_auth_len, len(data)) + self._one_time_auth_buff_data += data[:length] + data = data[length:] + if len(self._one_time_auth_buff_data) == self._one_time_auth_len: + # get a chunk data + if onetimeauth_verify(self._one_time_auth_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:], + self._one_time_auth_buff_data, + self._encryptor.decipher_iv + struct.pack('>I', self._one_time_auth_chunk_idx)) \ + is False: + # + logging.warn('one time auth fail, drop chunk !') + else: + data_cb(self._one_time_auth_buff_data) + self._one_time_auth_buff_head = '' + self._one_time_auth_buff_data = '' + self._one_time_auth_chunk_idx += 1 + self._one_time_auth_len = 0 + return + + def _handle_stage_stream(self, data): + if self._is_local: + data = self._encryptor.encrypt(data) + if self._one_time_auth_enable: + self._one_time_auth_chunk_data(data, self._write_to_sock_remote) + else: + self._write_to_sock(data, self._remote_sock) + return + def _on_local_read(self): # handle all local read events and dispatch them to methods for # each stage @@ -406,9 +477,7 @@ class TCPRelayHandler(object): if not data: return if self._stage == STAGE_STREAM: - if self._is_local: - data = self._encryptor.encrypt(data) - self._write_to_sock(data, self._remote_sock) + self._handle_stage_stream(data) return elif is_local and self._stage == STAGE_INIT: # TODO check auth method diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index ab12c54..bef46af 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -69,7 +69,8 @@ import errno import random from shadowsocks import encrypt, eventloop, lru_cache, common, shell -from shadowsocks.common import parse_header, pack_addr +from shadowsocks.common import parse_header, pack_addr, \ + ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH BUF_SIZE = 65536 @@ -97,6 +98,10 @@ class UDPRelay(object): self._password = common.to_bytes(config['password']) self._method = config['method'] self._timeout = config['timeout'] + if 'one_time_auth' in config and config['one_time_auth']: + self._one_time_auth_enable = True + else: + self._one_time_auth_enable = False self._is_local = is_local self._cache = lru_cache.LRUCache(timeout=config['timeout'], close_callback=self._close_client) @@ -243,7 +248,19 @@ class UDPRelay(object): header_result = parse_header(data) if header_result is None: return - # addrtype, dest_addr, dest_port, header_length = header_result + addrtype, dest_addr, dest_port, header_length = header_result + # spec https://shadowsocks.org/en/spec/one-time-auth.html + if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: + if len(data) < header_length + ONETIMEAUTH_BYTES: + logging.warn('one time auth header is too short') + return None + if onetimeauth_verify(data[-ONETIMEAUTH_BYTES:], + data[header_length: -ONETIMEAUTH_BYTES], + self._encryptor.decipher_iv + self._encryptor.key) is False: + logging.warn('one time auth fail') + return None + self._one_time_authed = True + header_length += ONETIMEAUTH_BYTES response = b'\x00\x00\x00' + data client_addr = self._client_fd_to_server_addr.get(sock.fileno()) if client_addr: From 77b2a2205597ba80f016b2c0bdc03fed3ee5bd34 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 10:11:31 +0800 Subject: [PATCH 125/196] ota completed! --- shadowsocks/common.py | 2 ++ shadowsocks/shell.py | 4 +-- shadowsocks/tcprelay.py | 57 ++++++++++++++++++++++++++--------------- shadowsocks/udprelay.py | 36 +++++++++++++++----------- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 7ec04ba..9dd5f7c 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -37,6 +37,8 @@ def sha1_hmac(secret, data): def onetimeauth_verify(_hash, data, key): return _hash == sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] +def onetimeauth_gen(data, key): + return sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] def compat_ord(s): if type(s) == int: diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index e2284da..336d918 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -130,11 +130,11 @@ def get_config(is_local): logging.basicConfig(level=logging.INFO, format='%(levelname)-s: %(message)s') if is_local: - shortopts = 'hd:s:b:p:k:l:m:c:t:vq' + shortopts = 'hd:s:b:p:k:l:m:c:t:vqa' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=', 'version'] else: - shortopts = 'hd:s:p:k:m:c:t:vq' + shortopts = 'hd:s:p:k:m:c:t:vqa' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', 'forbidden-ip=', 'user=', 'manager-address=', 'version'] try: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 22ba104..c61f8c0 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -27,7 +27,7 @@ import traceback import random from shadowsocks import encrypt, eventloop, shell, common -from shadowsocks.common import parse_header, onetimeauth_verify, \ +from shadowsocks.common import parse_header, onetimeauth_verify, onetimeauth_gen, \ ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time @@ -233,12 +233,14 @@ class TCPRelayHandler(object): def _handle_stage_connecting(self, data): if self._is_local: + if self._one_time_auth_enable: + data = self._one_time_auth_chunk_data_gen(data) data = self._encryptor.encrypt(data) - if self._one_time_auth_enable: - self._one_time_auth_chunk_data(data, - self._data_to_write_to_remote.append) - else: self._data_to_write_to_remote.append(data) + else: + if self._one_time_auth_enable: + self._one_time_auth_chunk_data(data, + self._data_to_write_to_remote.append) if self._is_local and not self._fastopen_connected and \ self._config['fast_open']: # for sslocal and fastopen, we basically wait for data and use @@ -306,17 +308,18 @@ 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])) - # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: - if len(data) < header_length + ONETIMEAUTH_BYTES: - logging.warn('one time auth header is too short') - return None - if onetimeauth_verify(data[header_length: header_length+ONETIMEAUTH_BYTES], - data[:header_length], - self._encryptor.decipher_iv + self._encryptor.key) is False: - logging.warn('one time auth fail') - self.destroy() - header_length += ONETIMEAUTH_BYTES + if self._is_local is False: + # spec https://shadowsocks.org/en/spec/one-time-auth.html + if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: + if len(data) < header_length + ONETIMEAUTH_BYTES: + logging.warn('one time auth header is too short') + return None + if onetimeauth_verify(data[header_length: header_length+ONETIMEAUTH_BYTES], + data[:header_length], + self._encryptor.decipher_iv + self._encryptor.key) is False: + logging.warn('one time auth fail') + self.destroy() + header_length += ONETIMEAUTH_BYTES self._remote_address = (common.to_str(remote_addr), remote_port) # pause reading self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) @@ -326,6 +329,11 @@ class TCPRelayHandler(object): self._write_to_sock((b'\x05\x00\x00\x01' b'\x00\x00\x00\x00\x10\x10'), self._local_sock) + # spec https://shadowsocks.org/en/spec/one-time-auth.html + # ATYP & 0x10 == 1, then OTA is enabled. + if self._one_time_auth_enable: + data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] + data += onetimeauth_gen(data, self._encryptor.cipher_iv + self._encryptor.key) data_to_send = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data_to_send) # notice here may go into _handle_dns_resolved directly @@ -436,23 +444,30 @@ class TCPRelayHandler(object): self._one_time_auth_buff_data, self._encryptor.decipher_iv + struct.pack('>I', self._one_time_auth_chunk_idx)) \ is False: - # logging.warn('one time auth fail, drop chunk !') else: data_cb(self._one_time_auth_buff_data) + self._one_time_auth_chunk_idx += 1 self._one_time_auth_buff_head = '' self._one_time_auth_buff_data = '' - self._one_time_auth_chunk_idx += 1 self._one_time_auth_len = 0 return + def _one_time_auth_chunk_data_gen(self, data): + data_len = struct.pack(">H", len(data)) + sha110 = onetimeauth_gen(data, self._encryptor.cipher_iv + struct.pack('>I', self._one_time_auth_chunk_idx)) + self._one_time_auth_chunk_idx += 1 + return data_len + sha110 + data + def _handle_stage_stream(self, data): if self._is_local: + if self._one_time_auth_enable: + data = self._one_time_auth_chunk_data_gen(data) data = self._encryptor.encrypt(data) - if self._one_time_auth_enable: - self._one_time_auth_chunk_data(data, self._write_to_sock_remote) - else: self._write_to_sock(data, self._remote_sock) + else: + if self._one_time_auth_enable: + self._one_time_auth_chunk_data(data, self._write_to_sock_remote) return def _on_local_read(self): diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index bef46af..0a92102 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -119,7 +119,7 @@ class UDPRelay(object): addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if len(addrs) == 0: - raise Exception("can't get addrinfo for %s:%d" % + raise Exception("UDP can't get addrinfo for %s:%d" % (self._listen_addr, self._listen_port)) af, socktype, proto, canonname, sa = addrs[0] server_socket = socket.socket(af, socktype, proto) @@ -157,7 +157,7 @@ class UDPRelay(object): if self._is_local: frag = common.ord(data[2]) if frag != 0: - logging.warn('drop a message since frag is not 0') + logging.warn('UDP drop a message since frag is not 0') return else: data = data[3:] @@ -176,7 +176,18 @@ class UDPRelay(object): server_addr, server_port = self._get_a_server() else: server_addr, server_port = dest_addr, dest_port - + # spec https://shadowsocks.org/en/spec/one-time-auth.html + if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: + if len(data) < header_length + ONETIMEAUTH_BYTES: + logging.warn('UDP one time auth header is too short') + return None + if onetimeauth_verify(data[-ONETIMEAUTH_BYTES:], + data[header_length: -ONETIMEAUTH_BYTES], + self._encryptor.decipher_iv + self._encryptor.key) is False: + logging.warn('UDP one time auth fail') + return None + self._one_time_authed = True + header_length += ONETIMEAUTH_BYTES addrs = self._dns_cache.get(server_addr, None) if addrs is None: addrs = socket.getaddrinfo(server_addr, server_port, 0, @@ -207,6 +218,9 @@ class UDPRelay(object): self._eventloop.add(client, eventloop.POLL_IN, self) if self._is_local: + # spec https://shadowsocks.org/en/spec/one-time-auth.html + if self._one_time_auth_enable: + data = _one_time_auth_chunk_data_gen(data) data = encrypt.encrypt_all(self._password, self._method, 1, data) if not data: return @@ -249,18 +263,6 @@ class UDPRelay(object): if header_result is None: return addrtype, dest_addr, dest_port, header_length = header_result - # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: - if len(data) < header_length + ONETIMEAUTH_BYTES: - logging.warn('one time auth header is too short') - return None - if onetimeauth_verify(data[-ONETIMEAUTH_BYTES:], - data[header_length: -ONETIMEAUTH_BYTES], - self._encryptor.decipher_iv + self._encryptor.key) is False: - logging.warn('one time auth fail') - return None - self._one_time_authed = True - header_length += ONETIMEAUTH_BYTES response = b'\x00\x00\x00' + data client_addr = self._client_fd_to_server_addr.get(sock.fileno()) if client_addr: @@ -270,6 +272,10 @@ class UDPRelay(object): # simply drop that packet pass + def _one_time_auth_chunk_data_gen(self, data): + data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] + return data + onetimeauth_gen(data, self._encryptor.cipher_iv + self._encryptor.key) + def add_to_loop(self, loop): if self._eventloop: raise Exception('already add to loop') From ca99abff899451dbc45a45ce0e3b7c6d842b1ec2 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 10:26:51 +0800 Subject: [PATCH 126/196] fix udp ota --- shadowsocks/udprelay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 0a92102..d4b922f 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -69,7 +69,7 @@ import errno import random from shadowsocks import encrypt, eventloop, lru_cache, common, shell -from shadowsocks.common import parse_header, pack_addr, \ +from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, onetimeauth_gen, \ ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH @@ -180,12 +180,12 @@ class UDPRelay(object): if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('UDP one time auth header is too short') - return None + return if onetimeauth_verify(data[-ONETIMEAUTH_BYTES:], data[header_length: -ONETIMEAUTH_BYTES], self._encryptor.decipher_iv + self._encryptor.key) is False: logging.warn('UDP one time auth fail') - return None + return self._one_time_authed = True header_length += ONETIMEAUTH_BYTES addrs = self._dns_cache.get(server_addr, None) From 569ca0d0a43839d6ae521ae0aeb275fe7599b8bb Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 18:30:45 +0800 Subject: [PATCH 127/196] reverse config --- shadowsocks/server.py | 13 ++++++------- shadowsocks/shell.py | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 5896c7d..1be4c0f 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -42,13 +42,12 @@ def main(): 'will be ignored') else: config['port_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'] + 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'] if config.get('manager_address', 0): logging.info('entering manager mode') diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index eb1dc04..67bfd92 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -93,7 +93,7 @@ def check_config(config, is_local): if 'local_port' in config: config['local_port'] = int(config['local_port']) - if config.get('server_port', None) and type(config['server_port']) != list: + 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']: @@ -243,7 +243,7 @@ def get_config(is_local): except Exception as e: logging.error(e) sys.exit(2) - config['server_port'] = config.get('server_port', None) + config['server_port'] = config.get('server_port', 8388) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') From 7efc3e2640342f2e662fba71d5c45ced9276ae17 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 18:32:39 +0800 Subject: [PATCH 128/196] README.md --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index f621dfd..b61c0db 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -About shadowsocks-rm ----------------- - -This project is https://github.com/shadowsocks/shadowsocks clone. I JUST fix bug on the original code. Unless it is necessary to have additional features. - - shadowsocks =========== From 2d40a361db004fedb78edbb60054e9ab721f4827 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 18:42:23 +0800 Subject: [PATCH 129/196] ota config --- shadowsocks/shell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 67bfd92..380bf25 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -228,6 +228,7 @@ def get_config(is_local): 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) + config['one_time_auth'] = config.get('one_time_auth', False) if is_local: if config.get('server', None) is None: logging.error('server addr not specified') From ee391c777340cfc0e653e7a0a6647e7a32ce9bb8 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 22:58:53 +0800 Subject: [PATCH 130/196] udp ota bug --- shadowsocks/udprelay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index d4b922f..6f0341f 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -69,8 +69,8 @@ import errno import random from shadowsocks import encrypt, eventloop, lru_cache, common, shell -from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, onetimeauth_gen, \ - ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH +from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, \ + onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH BUF_SIZE = 65536 @@ -220,7 +220,7 @@ class UDPRelay(object): if self._is_local: # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._one_time_auth_enable: - data = _one_time_auth_chunk_data_gen(data) + data = self._one_time_auth_chunk_data_gen(data) data = encrypt.encrypt_all(self._password, self._method, 1, data) if not data: return From a4a87eb12703fa1e8f1668482f178861aa0065af Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 23:18:16 +0800 Subject: [PATCH 131/196] PEP8 --- shadowsocks/tcprelay.py | 176 +++++++++++++++++++++------------------- shadowsocks/udprelay.py | 14 ++-- 2 files changed, 100 insertions(+), 90 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index c61f8c0..f415b22 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -109,13 +109,13 @@ class TCPRelayHandler(object): self._encryptor = encrypt.Encryptor(config['password'], config['method']) if 'one_time_auth' in config and config['one_time_auth']: - self._one_time_auth_enable = True + self._ota_enable = True else: - self._one_time_auth_enable = False - self._one_time_auth_buff_head = '' - self._one_time_auth_buff_data = '' - self._one_time_auth_len = 0 - self._one_time_auth_chunk_idx = 0 + self._ota_enable = False + self._ota_buff_head = '' + self._ota_buff_data = '' + self._ota_len = 0 + self._ota_chunk_idx = 0 self._fastopen_connected = False self._data_to_write_to_local = [] self._data_to_write_to_remote = [] @@ -233,14 +233,14 @@ class TCPRelayHandler(object): def _handle_stage_connecting(self, data): if self._is_local: - if self._one_time_auth_enable: - data = self._one_time_auth_chunk_data_gen(data) + if self._ota_enable: + data = self._ota_chunk_data_gen(data) data = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data) else: - if self._one_time_auth_enable: - self._one_time_auth_chunk_data(data, - self._data_to_write_to_remote.append) + if self._ota_enable: + self._ota_chunk_data(data, + self._data_to_write_to_remote.append) if self._is_local and not self._fastopen_connected and \ self._config['fast_open']: # for sslocal and fastopen, we basically wait for data and use @@ -254,7 +254,8 @@ class TCPRelayHandler(object): 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) + s = remote_sock.sendto(data, MSG_FASTOPEN, + self._chosen_server) if s < l: data = data[s:] self._data_to_write_to_remote = [data] @@ -310,13 +311,15 @@ class TCPRelayHandler(object): self._client_address[0], self._client_address[1])) if self._is_local is False: # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: + if self._ota_enable or addrtype & ADDRTYPE_AUTH: if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('one time auth header is too short') return None - if onetimeauth_verify(data[header_length: header_length+ONETIMEAUTH_BYTES], - data[:header_length], - self._encryptor.decipher_iv + self._encryptor.key) is False: + offset = header_length + ONETIMEAUTH_BYTES + _hash = data[header_length: offset] + _data = data[:header_length] + key = self._encryptor.decipher_iv + self._encryptor.key + if onetimeauth_verify(_hash, _data, key) is False: logging.warn('one time auth fail') self.destroy() header_length += ONETIMEAUTH_BYTES @@ -331,19 +334,20 @@ class TCPRelayHandler(object): self._local_sock) # spec https://shadowsocks.org/en/spec/one-time-auth.html # ATYP & 0x10 == 1, then OTA is enabled. - if self._one_time_auth_enable: + if self._ota_enable: data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] - data += onetimeauth_gen(data, self._encryptor.cipher_iv + self._encryptor.key) + key = self._encryptor.cipher_iv + self._encryptor.key + data += onetimeauth_gen(data, key) data_to_send = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data_to_send) # notice here may go into _handle_dns_resolved directly self._dns_resolver.resolve(self._chosen_server[0], self._handle_dns_resolved) else: - if self._one_time_auth_enable: + if self._ota_enable: data = data[header_length:] - self._one_time_auth_chunk_data(data, - self._data_to_write_to_remote.append) + self._ota_chunk_data(data, + self._data_to_write_to_remote.append) elif len(data) > header_length: self._data_to_write_to_remote.append(data[header_length:]) # notice here may go into _handle_dns_resolved directly @@ -377,97 +381,99 @@ class TCPRelayHandler(object): self._log_error(error) self.destroy() return - if result: + if result and result[1]: ip = result[1] - if ip: - try: - self._stage = STAGE_CONNECTING - remote_addr = ip - if self._is_local: - remote_port = self._chosen_server[1] - else: - remote_port = self._remote_address[1] + try: + self._stage = STAGE_CONNECTING + remote_addr = ip + if self._is_local: + remote_port = self._chosen_server[1] + else: + remote_port = self._remote_address[1] - if self._is_local and self._config['fast_open']: - # for fastopen: - # wait for more data to arrive and send them in one SYN - self._stage = STAGE_CONNECTING - # we don't have to wait for remote since it's not - # created - self._update_stream(STREAM_UP, WAIT_STATUS_READING) - # TODO when there is already data in this packet - else: - # else do connect - remote_sock = self._create_remote_socket(remote_addr, - remote_port) - try: - remote_sock.connect((remote_addr, remote_port)) - except (OSError, IOError) as e: - if eventloop.errno_from_exception(e) == \ - errno.EINPROGRESS: - pass - self._loop.add(remote_sock, - 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) - return - except Exception as e: - shell.print_exception(e) - if self._config['verbose']: - traceback.print_exc() + if self._is_local and self._config['fast_open']: + # for fastopen: + # wait for more data arrive and send them in one SYN + self._stage = STAGE_CONNECTING + # we don't have to wait for remote since it's not + # created + self._update_stream(STREAM_UP, WAIT_STATUS_READING) + # TODO when there is already data in this packet + else: + # else do connect + remote_sock = self._create_remote_socket(remote_addr, + remote_port) + try: + remote_sock.connect((remote_addr, remote_port)) + except (OSError, IOError) as e: + if eventloop.errno_from_exception(e) == \ + errno.EINPROGRESS: + pass + self._loop.add(remote_sock, + 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) + return + except Exception as e: + shell.print_exception(e) + if self._config['verbose']: + traceback.print_exc() self.destroy() def _write_to_sock_remote(self, data): self._write_to_sock(data, self._remote_sock) - def _one_time_auth_chunk_data(self, data, data_cb): + def _ota_chunk_data(self, data, data_cb): # spec https://shadowsocks.org/en/spec/one-time-auth.html while len(data) > 0: - if self._one_time_auth_len == 0: + if self._ota_len == 0: # get DATA.LEN + HMAC-SHA1 - length = ONETIMEAUTH_CHUNK_BYTES - len(self._one_time_auth_buff_head) - self._one_time_auth_buff_head += data[:length] + length = ONETIMEAUTH_CHUNK_BYTES - len(self._ota_buff_head) + self._ota_buff_head += data[:length] data = data[length:] - if len(self._one_time_auth_buff_head) < ONETIMEAUTH_CHUNK_BYTES: + if len(self._ota_buff_head) < ONETIMEAUTH_CHUNK_BYTES: # wait more data return - self._one_time_auth_len = struct.unpack('>H', - self._one_time_auth_buff_head[:ONETIMEAUTH_CHUNK_DATA_LEN])[0] - length = min(self._one_time_auth_len, len(data)) - self._one_time_auth_buff_data += data[:length] + data_len = self._ota_buff_head[:ONETIMEAUTH_CHUNK_DATA_LEN] + self._ota_len = struct.unpack('>H', data_len)[0] + length = min(self._ota_len, len(data)) + self._ota_buff_data += data[:length] data = data[length:] - if len(self._one_time_auth_buff_data) == self._one_time_auth_len: + if len(self._ota_buff_data) == self._ota_len: # get a chunk data - if onetimeauth_verify(self._one_time_auth_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:], - self._one_time_auth_buff_data, - self._encryptor.decipher_iv + struct.pack('>I', self._one_time_auth_chunk_idx)) \ - is False: + _hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:] + _data = self._ota_buff_data + index = struct.pack('>I', self._ota_chunk_idx) + key = self._encryptor.decipher_iv + index + if onetimeauth_verify(_hash, _data, key) is False: logging.warn('one time auth fail, drop chunk !') else: - data_cb(self._one_time_auth_buff_data) - self._one_time_auth_chunk_idx += 1 - self._one_time_auth_buff_head = '' - self._one_time_auth_buff_data = '' - self._one_time_auth_len = 0 + data_cb(self._ota_buff_data) + self._ota_chunk_idx += 1 + self._ota_buff_head = '' + self._ota_buff_data = '' + self._ota_len = 0 return - def _one_time_auth_chunk_data_gen(self, data): + def _ota_chunk_data_gen(self, data): data_len = struct.pack(">H", len(data)) - sha110 = onetimeauth_gen(data, self._encryptor.cipher_iv + struct.pack('>I', self._one_time_auth_chunk_idx)) - self._one_time_auth_chunk_idx += 1 + index = struct.pack('>I', self._ota_chunk_idx) + key = self._encryptor.cipher_iv + index + sha110 = onetimeauth_gen(data, key) + self._ota_chunk_idx += 1 return data_len + sha110 + data def _handle_stage_stream(self, data): if self._is_local: - if self._one_time_auth_enable: - data = self._one_time_auth_chunk_data_gen(data) + if self._ota_enable: + data = self._ota_chunk_data_gen(data) data = self._encryptor.encrypt(data) self._write_to_sock(data, self._remote_sock) else: - if self._one_time_auth_enable: - self._one_time_auth_chunk_data(data, self._write_to_sock_remote) + if self._ota_enable: + self._ota_chunk_data(data, self._write_to_sock_remote) return def _on_local_read(self): diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 6f0341f..cbfaa31 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -165,7 +165,9 @@ class UDPRelay(object): data = encrypt.encrypt_all(self._password, self._method, 0, data) # decrypt data if not data: - logging.debug('UDP handle_server: data is empty after decrypt') + logging.debug( + 'UDP handle_server: data is empty after decrypt' + ) return header_result = parse_header(data) if header_result is None: @@ -181,9 +183,10 @@ class UDPRelay(object): if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('UDP one time auth header is too short') return - if onetimeauth_verify(data[-ONETIMEAUTH_BYTES:], - data[header_length: -ONETIMEAUTH_BYTES], - self._encryptor.decipher_iv + self._encryptor.key) is False: + _hash = data[-ONETIMEAUTH_BYTES:] + _data = data[header_length: -ONETIMEAUTH_BYTES] + _key = self._encryptor.decipher_iv + self._encryptor.key + if onetimeauth_verify(_hash, _data, _key) is False: logging.warn('UDP one time auth fail') return self._one_time_authed = True @@ -274,7 +277,8 @@ class UDPRelay(object): def _one_time_auth_chunk_data_gen(self, data): data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] - return data + onetimeauth_gen(data, self._encryptor.cipher_iv + self._encryptor.key) + key = self._encryptor.cipher_iv + self._encryptor.key + return data + onetimeauth_gen(data, key) def add_to_loop(self, loop): if self._eventloop: From 7c8ecb811ca7a4bce4932045e6a9b9811cf48d54 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 23:20:12 +0800 Subject: [PATCH 132/196] PEP8 --- shadowsocks/tcprelay.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index f415b22..cb3c413 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -27,8 +27,9 @@ import traceback import random from shadowsocks import encrypt, eventloop, shell, common -from shadowsocks.common import parse_header, onetimeauth_verify, onetimeauth_gen, \ - ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH +from shadowsocks.common import parse_header, onetimeauth_verify, \ + onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \ + ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time TIMEOUTS_CLEAN_SIZE = 512 From 614eed2bf0c8bf1c8e8e0c506331e8dd98be826b Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 23:27:21 +0800 Subject: [PATCH 133/196] PEP8 --- shadowsocks/common.py | 2 ++ shadowsocks/udprelay.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 9dd5f7c..ee14995 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -37,9 +37,11 @@ def sha1_hmac(secret, data): def onetimeauth_verify(_hash, data, key): return _hash == sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] + def onetimeauth_gen(data, key): return sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] + def compat_ord(s): if type(s) == int: return s diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index cbfaa31..6d29f63 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -184,7 +184,7 @@ class UDPRelay(object): logging.warn('UDP one time auth header is too short') return _hash = data[-ONETIMEAUTH_BYTES:] - _data = data[header_length: -ONETIMEAUTH_BYTES] + _data = data[header_length: -ONETIMEAUTH_BYTES] _key = self._encryptor.decipher_iv + self._encryptor.key if onetimeauth_verify(_hash, _data, _key) is False: logging.warn('UDP one time auth fail') From 285dc407294adfe1d3ce4107530f4259869b0701 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 23:47:47 +0800 Subject: [PATCH 134/196] here? --- tests/test_daemon.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_daemon.sh b/tests/test_daemon.sh index 40f35ef..1acddc0 100755 --- a/tests/test_daemon.sh +++ b/tests/test_daemon.sh @@ -18,7 +18,7 @@ function run_test { for module in local server do -command="coverage run -p -a shadowsocks/$module.py" +command="coverage run -a shadowsocks/$module.py" mkdir -p tmp From ab4206258775d494e86aba03a2aa5c990c96a625 Mon Sep 17 00:00:00 2001 From: jsy Date: Mon, 11 Jan 2016 23:57:18 +0800 Subject: [PATCH 135/196] Can't append to data files in parallel mode. --- tests/test_command.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_command.sh b/tests/test_command.sh index be05704..8225740 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -2,7 +2,7 @@ . tests/assert.sh -PYTHON="coverage run -a -p" +PYTHON="coverage run -a" LOCAL="$PYTHON shadowsocks/local.py" SERVER="$PYTHON shadowsocks/server.py" From 4786b9c3addd2b16664af20d53f8e08b498d013b Mon Sep 17 00:00:00 2001 From: jsy Date: Tue, 12 Jan 2016 00:03:13 +0800 Subject: [PATCH 136/196] Can't append to data files in parallel mode. --- tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index 29b57d4..85bd007 100755 --- a/tests/test.py +++ b/tests/test.py @@ -44,7 +44,7 @@ parser.add_argument('--dns', type=str, default='8.8.8.8') config = parser.parse_args() if config.with_coverage: - python = ['coverage', 'run', '-p', '-a'] + python = ['coverage', 'run', '-a'] client_args = python + ['shadowsocks/local.py', '-v'] server_args = python + ['shadowsocks/server.py', '-v'] From 3788df59bbd629fd4f38e219e6581678d6df276a Mon Sep 17 00:00:00 2001 From: jsy Date: Tue, 12 Jan 2016 00:04:40 +0800 Subject: [PATCH 137/196] Can't append to data files in parallel mode. --- tests/test_graceful_restart.sh | 2 +- tests/test_large_file.sh | 2 +- tests/test_udp_src.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh index d91ba92..eb4bc0f 100755 --- a/tests/test_graceful_restart.sh +++ b/tests/test_graceful_restart.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON="coverage run -p -a" +PYTHON="coverage run -a" URL=http://127.0.0.1/file diff --git a/tests/test_large_file.sh b/tests/test_large_file.sh index 33bcb59..7a6aec8 100755 --- a/tests/test_large_file.sh +++ b/tests/test_large_file.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON="coverage run -p -a" +PYTHON="coverage run -a" URL=http://127.0.0.1/file mkdir -p tmp diff --git a/tests/test_udp_src.sh b/tests/test_udp_src.sh index d356581..4a07a23 100755 --- a/tests/test_udp_src.sh +++ b/tests/test_udp_src.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON="coverage run -p -a" +PYTHON="coverage run -a" mkdir -p tmp From 9f3641db546121b6e120320871a5e6b9f1f99566 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 00:27:46 +0800 Subject: [PATCH 138/196] url ssl timeout --- tests/jenkins.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 5b53e93..dc583ba 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -51,7 +51,7 @@ run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 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" +run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://www.google.com/" # 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 d2f1caeb5d3458efe602878a6a94be5792b80629 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 01:03:25 +0800 Subject: [PATCH 139/196] fixbug: when ota disable data not write --- shadowsocks/tcprelay.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index cb3c413..20517c2 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -242,6 +242,8 @@ class TCPRelayHandler(object): if self._ota_enable: self._ota_chunk_data(data, self._data_to_write_to_remote.append) + else: + self._data_to_write_to_remote.append(data) if self._is_local and not self._fastopen_connected and \ self._config['fast_open']: # for sslocal and fastopen, we basically wait for data and use @@ -475,6 +477,8 @@ class TCPRelayHandler(object): else: if self._ota_enable: self._ota_chunk_data(data, self._write_to_sock_remote) + else: + self._write_to_sock(data, self._remote_sock) return def _on_local_read(self): From efad7578375cad323b5704c6827ad482fcc0f437 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 01:44:18 +0800 Subject: [PATCH 140/196] 9001 not idle? --- tests/test_udp_src.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index e8fa505..50af385 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -72,8 +72,8 @@ if __name__ == '__main__': 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)) + sock_in1.bind(('::1', 9004)) + sock_out.sendto(b'data', ('::1', 9004)) result3 = sock_in1.recvfrom(8) # make sure they're from different source ports From 288c11c9d9fe5a1389bb1f9e26727be3830e8308 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 01:55:42 +0800 Subject: [PATCH 141/196] CI Try again! --- tests/jenkins.sh | 2 +- tests/test_udp_src.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index dc583ba..5b53e93 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -51,7 +51,7 @@ run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 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://www.google.com/" +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" diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index 50af385..dd78ebf 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -48,13 +48,13 @@ if __name__ == '__main__': sock_in2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_in1.bind(('::1', 9001)) - sock_in2.bind(('::1', 9002)) + sock_in1.bind(('::1', 9005)) + sock_in2.bind(('::1', 9006)) - sock_out.sendto(b'data', ('::1', 9001)) + sock_out.sendto(b'data', ('::1', 9005)) result1 = sock_in1.recvfrom(8) - sock_out.sendto(b'data', ('::1', 9002)) + sock_out.sendto(b'data', ('::1', 9006)) result2 = sock_in2.recvfrom(8) sock_out.close() From 248582c9328b9ec23bbcfada9000ac6131c5bce7 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 10:34:48 +0800 Subject: [PATCH 142/196] remove IPv6 testcase and add ota test --- shadowsocks/rc4-md5-ota.json | 11 +++++++++++ tests/jenkins.sh | 4 +++- tests/rc4-md5-ota.json | 12 ++++++++++++ tests/test_udp_src.py | 14 ++++++++------ 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 shadowsocks/rc4-md5-ota.json create mode 100644 tests/rc4-md5-ota.json diff --git a/shadowsocks/rc4-md5-ota.json b/shadowsocks/rc4-md5-ota.json new file mode 100644 index 0000000..164ee5a --- /dev/null +++ b/shadowsocks/rc4-md5-ota.json @@ -0,0 +1,11 @@ +{ + "server":"127.0.0.1", + "server_port":8899, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"rc4-md5", + "local_address":"127.0.0.1", + "fast_open":false, + "one_time_auth":true +} diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 5b53e93..6d0fac8 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -45,7 +45,9 @@ run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client- run_test python tests/test.py --with-coverage -s tests/server-dnsserver.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 +run_test python tests/test.py --with-coverage -c tests/rc4-md5-ota.json +# travis-ci not support IPv6 +# 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" diff --git a/tests/rc4-md5-ota.json b/tests/rc4-md5-ota.json new file mode 100644 index 0000000..ea93226 --- /dev/null +++ b/tests/rc4-md5-ota.json @@ -0,0 +1,12 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"rc4-md5", + "local_address":"127.0.0.1", + "fast_open":false, + "one_time_auth":true, + "verbose":1 +} diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index dd78ebf..585606d 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -36,6 +36,7 @@ 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, @@ -48,13 +49,13 @@ if __name__ == '__main__': sock_in2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_in1.bind(('::1', 9005)) - sock_in2.bind(('::1', 9006)) + sock_in1.bind(('::1', 9001)) + sock_in2.bind(('::1', 9002)) - sock_out.sendto(b'data', ('::1', 9005)) + sock_out.sendto(b'data', ('::1', 9001)) result1 = sock_in1.recvfrom(8) - sock_out.sendto(b'data', ('::1', 9006)) + sock_out.sendto(b'data', ('::1', 9002)) result2 = sock_in2.recvfrom(8) sock_out.close() @@ -72,8 +73,8 @@ if __name__ == '__main__': sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_in1.bind(('::1', 9004)) - sock_out.sendto(b'data', ('::1', 9004)) + 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 @@ -81,3 +82,4 @@ if __name__ == '__main__': sock_out.close() sock_in1.close() + """ From 269e3dd82dd2c670624de83afa6fdd60bfd6b040 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 12:10:40 +0800 Subject: [PATCH 143/196] test pass ota in udp --- shadowsocks/encrypt.py | 48 ++++++++++++++++++++++++++++++++++++ shadowsocks/rc4-md5-ota.json | 11 --------- shadowsocks/udprelay.py | 21 ++++++++-------- tests/rc4-md5-ota.json | 3 +-- 4 files changed, 60 insertions(+), 23 deletions(-) delete mode 100644 shadowsocks/rc4-md5-ota.json diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index 54dbb42..ece72ec 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -133,6 +133,42 @@ class Encryptor(object): return self.decipher.update(buf) +def gen_key_iv(password, method): + method = method.lower() + (key_len, iv_len, m) = method_supported[method] + key = None + if key_len > 0: + key, _ = EVP_BytesToKey(password, key_len, iv_len) + else: + key = password + iv = random_string(iv_len) + return key, iv, m + + +def encrypt_all_m(key, iv, m, method, data): + result = [] + result.append(iv) + cipher = m(method, key, iv, 1) + result.append(cipher.update(data)) + return b''.join(result) + + +def dencrypt_all(password, method, data): + result = [] + method = method.lower() + (key_len, iv_len, m) = method_supported[method] + key = None + if key_len > 0: + key, _ = EVP_BytesToKey(password, key_len, iv_len) + else: + key = password + iv = data[:iv_len] + data = data[iv_len:] + cipher = m(method, key, iv, 0) + result.append(cipher.update(data)) + return b''.join(result), key, iv + + def encrypt_all(password, method, op, data): result = [] method = method.lower() @@ -185,6 +221,18 @@ def test_encrypt_all(): assert plain == plain2 +def test_encrypt_all_m(): + from os import urandom + plain = urandom(10240) + for method in CIPHERS_TO_TEST: + logging.warn(method) + key, iv, m = gen_key_iv(b'key', method) + cipher = encrypt_all_m(key, iv, m, method, plain) + plain2, key, iv = dencrypt_all(b'key', method, cipher) + assert plain == plain2 + + if __name__ == '__main__': test_encrypt_all() test_encryptor() + test_encrypt_all_m() diff --git a/shadowsocks/rc4-md5-ota.json b/shadowsocks/rc4-md5-ota.json deleted file mode 100644 index 164ee5a..0000000 --- a/shadowsocks/rc4-md5-ota.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8899, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"rc4-md5", - "local_address":"127.0.0.1", - "fast_open":false, - "one_time_auth":true -} diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 6d29f63..975906e 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -150,6 +150,8 @@ class UDPRelay(object): def _handle_server(self): server = self._server_socket data, r_addr = server.recvfrom(BUF_SIZE) + key = None + iv = None if not data: logging.debug('UDP handle_server: data is empty') if self._stat_callback: @@ -162,7 +164,7 @@ class UDPRelay(object): else: data = data[3:] else: - data = encrypt.encrypt_all(self._password, self._method, 0, data) + data, key, iv = encrypt.dencrypt_all(self._password, self._method, data) # decrypt data if not data: logging.debug( @@ -184,13 +186,11 @@ class UDPRelay(object): logging.warn('UDP one time auth header is too short') return _hash = data[-ONETIMEAUTH_BYTES:] - _data = data[header_length: -ONETIMEAUTH_BYTES] - _key = self._encryptor.decipher_iv + self._encryptor.key - if onetimeauth_verify(_hash, _data, _key) is False: + data = data[: -ONETIMEAUTH_BYTES] + _key = iv + key + if onetimeauth_verify(_hash, data, _key) is False: logging.warn('UDP one time auth fail') return - self._one_time_authed = True - header_length += ONETIMEAUTH_BYTES addrs = self._dns_cache.get(server_addr, None) if addrs is None: addrs = socket.getaddrinfo(server_addr, server_port, 0, @@ -221,10 +221,11 @@ class UDPRelay(object): self._eventloop.add(client, eventloop.POLL_IN, self) if self._is_local: + key, iv, m = gen_key_iv(self._password, self._method) # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._one_time_auth_enable: - data = self._one_time_auth_chunk_data_gen(data) - data = encrypt.encrypt_all(self._password, self._method, 1, data) + data = self._ota_chunk_data_gen(key, iv, data) + data = encrypt.encrypt_all_m(key, iv, m, method, data) if not data: return else: @@ -275,9 +276,9 @@ class UDPRelay(object): # simply drop that packet pass - def _one_time_auth_chunk_data_gen(self, data): + def _ota_chunk_data_gen(self, key, iv, data): data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] - key = self._encryptor.cipher_iv + self._encryptor.key + key = iv + key return data + onetimeauth_gen(data, key) def add_to_loop(self, loop): diff --git a/tests/rc4-md5-ota.json b/tests/rc4-md5-ota.json index ea93226..3566d6d 100644 --- a/tests/rc4-md5-ota.json +++ b/tests/rc4-md5-ota.json @@ -7,6 +7,5 @@ "method":"rc4-md5", "local_address":"127.0.0.1", "fast_open":false, - "one_time_auth":true, - "verbose":1 + "one_time_auth":true } From f163a7b094d4fa6f98f9d595fdd47f6f67f58769 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 12:17:11 +0800 Subject: [PATCH 144/196] add CI --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b61c0db..608a1fd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ shadowsocks =========== +|PyPI version| |Build Status| |Coverage Status| + A fast tunnel proxy that helps you bypass firewalls. Features: From 811a0e6eb8f67a4f97a3a6f610df146385fd5786 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 12:17:46 +0800 Subject: [PATCH 145/196] PEP8 and undefined name 'gen_key_iv' --- shadowsocks/udprelay.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 975906e..3fbd9f3 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -164,7 +164,9 @@ class UDPRelay(object): else: data = data[3:] else: - data, key, iv = encrypt.dencrypt_all(self._password, self._method, data) + data, key, iv = encrypt.dencrypt_all(self._password, + self._method, + data) # decrypt data if not data: logging.debug( @@ -221,11 +223,11 @@ class UDPRelay(object): self._eventloop.add(client, eventloop.POLL_IN, self) if self._is_local: - key, iv, m = gen_key_iv(self._password, self._method) + key, iv, m = encrypt.gen_key_iv(self._password, self._method) # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._one_time_auth_enable: data = self._ota_chunk_data_gen(key, iv, data) - data = encrypt.encrypt_all_m(key, iv, m, method, data) + data = encrypt.encrypt_all_m(key, iv, m, self._method, data) if not data: return else: From a8d116d70a40c32fb234e813943bc09145512893 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 14:20:46 +0800 Subject: [PATCH 146/196] fix bug with ord in Py3 --- shadowsocks/tcprelay.py | 10 +++++----- shadowsocks/udprelay.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 20517c2..6430f26 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -113,8 +113,8 @@ class TCPRelayHandler(object): self._ota_enable = True else: self._ota_enable = False - self._ota_buff_head = '' - self._ota_buff_data = '' + self._ota_buff_head = b'' + self._ota_buff_data = b'' self._ota_len = 0 self._ota_chunk_idx = 0 self._fastopen_connected = False @@ -338,7 +338,7 @@ class TCPRelayHandler(object): # spec https://shadowsocks.org/en/spec/one-time-auth.html # ATYP & 0x10 == 1, then OTA is enabled. if self._ota_enable: - data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] + data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] key = self._encryptor.cipher_iv + self._encryptor.key data += onetimeauth_gen(data, key) data_to_send = self._encryptor.encrypt(data) @@ -455,8 +455,8 @@ class TCPRelayHandler(object): else: data_cb(self._ota_buff_data) self._ota_chunk_idx += 1 - self._ota_buff_head = '' - self._ota_buff_data = '' + self._ota_buff_head = b'' + self._ota_buff_data = b'' self._ota_len = 0 return diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 3fbd9f3..6bf6ce6 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -279,7 +279,7 @@ class UDPRelay(object): pass def _ota_chunk_data_gen(self, key, iv, data): - data = chr(ord(data[0]) | ADDRTYPE_AUTH) + data[1:] + data = common.chr(common.ord(data[0]) | ADDRTYPE_AUTH) + data[1:] key = iv + key return data + onetimeauth_gen(data, key) From efe39a75f9cfead70f6925b01d447fd9246fbb0b Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 14:22:10 +0800 Subject: [PATCH 147/196] README CI --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 608a1fd..ed06dba 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ shadowsocks =========== -|PyPI version| |Build Status| |Coverage Status| +[![PyPI version]][PyPI] +[![Build Status]][Travis CI] +[![Coverage Status]][Coverage] A fast tunnel proxy that helps you bypass firewalls. From b9766ce5df1af306f690279d8eeb09bb99751ec9 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Tue, 12 Jan 2016 14:38:10 +0800 Subject: [PATCH 148/196] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index ed06dba..fb1ffca 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,17 @@ License ------- Apache License + + + + + + + +[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat +[Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks +[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html +[PyPI]: https://pypi.python.org/pypi/shadowsocks +[PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat +[Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks + From b276d52735ce848202b8d1ea82e1f088216c0655 Mon Sep 17 00:00:00 2001 From: htc Date: Sat, 20 Feb 2016 10:37:25 +0800 Subject: [PATCH 149/196] Add support for option 'prefer-ipv6' --- debian/config.json | 3 ++- shadowsocks/asyncdns.py | 29 +++++++++++++++++------------ shadowsocks/server.py | 5 +++-- shadowsocks/shell.py | 7 ++++++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/debian/config.json b/debian/config.json index 35cb14a..7846cb2 100644 --- a/debian/config.json +++ b/debian/config.json @@ -7,5 +7,6 @@ "timeout":300, "method":"aes-256-cfb", "fast_open": false, - "workers": 1 + "workers": 1, + "prefer_ipv6": false } \ No newline at end of file diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 25a0e6b..44528d7 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -242,13 +242,13 @@ class DNSResponse(object): return '%s: %s' % (self.hostname, str(self.answers)) -STATUS_IPV4 = 0 -STATUS_IPV6 = 1 +STATUS_FIRST = 0 +STATUS_SECOND = 1 class DNSResolver(object): - def __init__(self, server_list=None): + def __init__(self, server_list=None, prefer_ipv6=False): self._loop = None self._hosts = {} self._hostname_status = {} @@ -261,6 +261,10 @@ class DNSResolver(object): self._parse_resolv() else: self._servers = server_list + if prefer_ipv6: + self._QTYPES = [QTYPE_AAAA, QTYPE_A] + else: + self._QTYPES = [QTYPE_A, QTYPE_AAAA] self._parse_hosts() # TODO monitor hosts change and reload hosts # TODO parse /etc/gai.conf and follow its rules @@ -341,17 +345,18 @@ class DNSResolver(object): answer[2] == QCLASS_IN: ip = answer[0] break - if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \ - == STATUS_IPV4: - self._hostname_status[hostname] = STATUS_IPV6 - self._send_req(hostname, QTYPE_AAAA) + if not ip and self._hostname_status.get(hostname, STATUS_SECOND) \ + == STATUS_FIRST: + self._hostname_status[hostname] = STATUS_SECOND + self._send_req(hostname, self._QTYPES[1]) else: if ip: self._cache[hostname] = ip self._call_callback(hostname, ip) - elif self._hostname_status.get(hostname, None) == STATUS_IPV6: + elif self._hostname_status.get(hostname, None) \ + == STATUS_SECOND: for question in response.questions: - if question[1] == QTYPE_AAAA: + if question[1] == self._QTYPES[1]: self._call_callback(hostname, None) break @@ -417,14 +422,14 @@ class DNSResolver(object): return arr = self._hostname_to_cb.get(hostname, None) if not arr: - self._hostname_status[hostname] = STATUS_IPV4 - self._send_req(hostname, QTYPE_A) + self._hostname_status[hostname] = STATUS_FIRST + self._send_req(hostname, self._QTYPES[0]) self._hostname_to_cb[hostname] = [callback] self._cb_to_hostname[callback] = hostname else: arr.append(callback) # TODO send again only if waited too long - self._send_req(hostname, QTYPE_A) + self._send_req(hostname, self._QTYPES[0]) def close(self): if self._sock: diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 1be4c0f..4dc5621 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -58,9 +58,10 @@ def main(): udp_servers = [] if 'dns_server' in config: # allow override settings in resolv.conf - dns_resolver = asyncdns.DNSResolver(config['dns_server']) + dns_resolver = asyncdns.DNSResolver(config['dns_server'], + config['prefer_ipv6']) else: - dns_resolver = asyncdns.DNSResolver() + dns_resolver = asyncdns.DNSResolver(prefer_ipv6=config['prefer_ipv6']) port_password = config['port_password'] del config['port_password'] diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 380bf25..42efbc0 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -137,7 +137,8 @@ def get_config(is_local): else: shortopts = 'hd:s:p:k:m:c:t:vqa' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user=', 'manager-address=', 'version'] + 'forbidden-ip=', 'user=', 'manager-address=', 'version', + 'prefer-ipv6'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -207,6 +208,8 @@ def get_config(is_local): elif key == '-q': v_count -= 1 config['verbose'] = v_count + elif key == '--prefer-ipv6': + config['prefer_ipv6'] = True except getopt.GetoptError as e: print(e, file=sys.stderr) print_help(is_local) @@ -229,6 +232,7 @@ def get_config(is_local): config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) config['local_port'] = config.get('local_port', 1080) config['one_time_auth'] = config.get('one_time_auth', False) + config['prefer_ipv6'] = config.get('prefer_ipv6', False) if is_local: if config.get('server', None) is None: logging.error('server addr not specified') @@ -324,6 +328,7 @@ Proxy options: --workers WORKERS number of workers, available on Unix/Linux --forbidden-ip IPLIST comma seperated IP list forbidden to connect --manager-address ADDR optional server manager UDP address, see wiki + --prefer-ipv6 resolve ipv6 address first General options: -h, --help show this help message and exit From 9d6b9fcde0f1e6511a7ef067868fafe9c0c672a3 Mon Sep 17 00:00:00 2001 From: htc550605125 Date: Tue, 23 Feb 2016 20:47:04 -0500 Subject: [PATCH 150/196] add dockerfile --- Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..36ddaa0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM stackbrew/debian:jessie +RUN apt-get update +RUN apt-get install -y python python-setuptools + +ADD . /shadowsocks + +WORKDIR /shadowsocks +RUN python setup.py install +CMD ssserver From 1f24b31f891e237cf17949dfc42e280bc077c8cc Mon Sep 17 00:00:00 2001 From: mengskysama Date: Sat, 16 Apr 2016 11:07:22 +0800 Subject: [PATCH 151/196] Update install.sh replace dante corrupt src --- tests/socksify/install.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/socksify/install.sh b/tests/socksify/install.sh index 8eff72d..9de6458 100755 --- a/tests/socksify/install.sh +++ b/tests/socksify/install.sh @@ -1,8 +1,11 @@ #!/bin/bash if [ ! -d dante-1.4.0 ]; then - wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 + #wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 + wget https://codeload.github.com/notpeter/dante/tar.gz/dante-1.4.0 -O dante-1.4.0.tar.gz || exit 1 tar xf dante-1.4.0.tar.gz || exit 1 + # + mv dante-dante-1.4.0 dante-1.4.0 fi pushd dante-1.4.0 ./configure && make -j4 && make install || exit 1 From bb53b0cb901d87daafae713bba902ecb6e713506 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Sat, 16 Apr 2016 11:46:06 +0800 Subject: [PATCH 152/196] fix build dante cache and old src corrupt and slow --- tests/socksify/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/socksify/install.sh b/tests/socksify/install.sh index 9de6458..86c4e1d 100755 --- a/tests/socksify/install.sh +++ b/tests/socksify/install.sh @@ -1,6 +1,7 @@ #!/bin/bash -if [ ! -d dante-1.4.0 ]; then +if [ ! -d dante-1.4.0 ] || [ ! -d dante-1.4.0/configure ]; then + rm dante-1.4.0 -rf #wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 wget https://codeload.github.com/notpeter/dante/tar.gz/dante-1.4.0 -O dante-1.4.0.tar.gz || exit 1 tar xf dante-1.4.0.tar.gz || exit 1 From aae990a2fc6ea4c5709e1a44f1e02ca1a2be29fe Mon Sep 17 00:00:00 2001 From: watermeter Date: Sat, 16 Apr 2016 11:54:12 +0800 Subject: [PATCH 153/196] Add auth method check for TCP socks connections (#528) * Add auth method check for TCP socks connections * remove meaningless return at end of function * remove extra blank lines --- shadowsocks/tcprelay.py | 54 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 6430f26..cf45351 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -36,6 +36,9 @@ TIMEOUTS_CLEAN_SIZE = 512 MSG_FASTOPEN = 0x20000000 +# SOCKS METHOD definition +METHOD_NOAUTH = 0 + # SOCKS command definition CMD_CONNECT = 1 CMD_BIND = 2 @@ -52,7 +55,7 @@ CMD_UDP_ASSOCIATE = 3 # for each handler, it could be at one of several stages: # as sslocal: -# stage 0 SOCKS hello received from local, send hello to local +# stage 0 auth METHOD received from local, reply with selection message # stage 1 addr received from local, query DNS for remote # stage 2 UDP assoc # stage 3 DNS resolved, connect to remote @@ -92,6 +95,14 @@ WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING BUF_SIZE = 32 * 1024 +# helper exceptions for TCPRelayHandler + +class BadSocksHeader(Exception): + pass + +class NoAcceptableMethods(Exception): + pass + class TCPRelayHandler(object): def __init__(self, server, fd_to_handlers, loop, local_sock, config, dns_resolver, is_local): @@ -481,6 +492,42 @@ class TCPRelayHandler(object): self._write_to_sock(data, self._remote_sock) return + def _check_auth_method(self, data): + # VER, NMETHODS, and at least 1 METHODS + if len(data) < 3: + logging.warning('method selection header too short') + raise BadSocksHeader + socks_version = common.ord(data[0]) + nmethods = common.ord(data[1]) + if socks_version != 5: + logging.warning('unsupported SOCKS protocol version ' + str(socks_version)) + raise BadSocksHeader + if nmethods < 1 or len(data) != nmethods + 2: + logging.warning('NMETHODS and number of METHODS mismatch') + raise BadSocksHeader + noauth_exist = False + for method in data[2:]: + if common.ord(method) == METHOD_NOAUTH: + noauth_exist = True + break + if not noauth_exist: + logging.warning('none of METHOD\'s requested by client is supported') + raise NoAcceptableMethods + + def _handle_stage_init(self, data): + try: + self._check_auth_method(data) + except BadSocksHeader: + self.destroy() + return + except NoAcceptableMethods: + self._write_to_sock(b'\x05\xff', self._local_sock) + self.destroy() + return + + self._write_to_sock(b'\x05\00', self._local_sock) + self._stage = STAGE_ADDR + def _on_local_read(self): # handle all local read events and dispatch them to methods for # each stage @@ -506,10 +553,7 @@ class TCPRelayHandler(object): self._handle_stage_stream(data) return elif is_local and self._stage == STAGE_INIT: - # TODO check auth method - self._write_to_sock(b'\x05\00', self._local_sock) - self._stage = STAGE_ADDR - return + self._handle_stage_init(data) elif self._stage == STAGE_CONNECTING: self._handle_stage_connecting(data) elif (is_local and self._stage == STAGE_ADDR) or \ From f62a550e9f8351ab3dc9d57e0fb554d5022b3d81 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Sat, 16 Apr 2016 12:13:59 +0800 Subject: [PATCH 154/196] pep8 --- shadowsocks/tcprelay.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index cf45351..5100f1c 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -100,9 +100,11 @@ BUF_SIZE = 32 * 1024 class BadSocksHeader(Exception): pass + class NoAcceptableMethods(Exception): pass + class TCPRelayHandler(object): def __init__(self, server, fd_to_handlers, loop, local_sock, config, dns_resolver, is_local): @@ -500,7 +502,8 @@ class TCPRelayHandler(object): socks_version = common.ord(data[0]) nmethods = common.ord(data[1]) if socks_version != 5: - logging.warning('unsupported SOCKS protocol version ' + str(socks_version)) + logging.warning('unsupported SOCKS protocol version ' + + str(socks_version)) raise BadSocksHeader if nmethods < 1 or len(data) != nmethods + 2: logging.warning('NMETHODS and number of METHODS mismatch') @@ -511,7 +514,8 @@ class TCPRelayHandler(object): noauth_exist = True break if not noauth_exist: - logging.warning('none of METHOD\'s requested by client is supported') + logging.warning('none of SOCKS METHOD\'s ' + 'requested by client is supported') raise NoAcceptableMethods def _handle_stage_init(self, data): From 9844ba9dc75028e1bbe7ea02e7ba7f4b181bf178 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Sat, 16 Apr 2016 12:19:32 +0800 Subject: [PATCH 155/196] pep8 --- shadowsocks/tcprelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 5100f1c..1387ac7 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -502,7 +502,7 @@ class TCPRelayHandler(object): socks_version = common.ord(data[0]) nmethods = common.ord(data[1]) if socks_version != 5: - logging.warning('unsupported SOCKS protocol version ' + + logging.warning('unsupported SOCKS protocol version ' + str(socks_version)) raise BadSocksHeader if nmethods < 1 or len(data) != nmethods + 2: From 677e6c4c563328191ca513d2f766574325d8a6eb Mon Sep 17 00:00:00 2001 From: v3aqb Date: Sun, 15 May 2016 01:27:21 +0800 Subject: [PATCH 156/196] fix ota --- shadowsocks/tcprelay.py | 1 + shadowsocks/udprelay.py | 1 + 2 files changed, 2 insertions(+) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 1387ac7..cc49d67 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -328,6 +328,7 @@ class TCPRelayHandler(object): if self._is_local is False: # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._ota_enable or addrtype & ADDRTYPE_AUTH: + self._ota_enable = True if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('one time auth header is too short') return None diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 6bf6ce6..849f611 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -184,6 +184,7 @@ class UDPRelay(object): server_addr, server_port = dest_addr, dest_port # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: + self._one_time_auth_enable = True if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('UDP one time auth header is too short') return From f7afcd4df067a4fb5e31f2fedbd475081aba92a3 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Mon, 18 Jul 2016 23:00:00 +0800 Subject: [PATCH 157/196] add ota help in local --- shadowsocks/shell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 42efbc0..6cca837 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -294,6 +294,7 @@ Proxy options: -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb -t TIMEOUT timeout in seconds, default: 300 + -a ONE_TIME_AUTH one time auth --fast-open use TCP_FASTOPEN, requires Linux 3.7+ General options: From 8e8ee5d490ce319b8db9b61001dac51a7da4be63 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Mon, 18 Jul 2016 23:31:51 +0800 Subject: [PATCH 158/196] Update tcprelay.py one time auth fail should be return --- shadowsocks/tcprelay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index cc49d67..075a50b 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -339,6 +339,7 @@ class TCPRelayHandler(object): if onetimeauth_verify(_hash, _data, key) is False: logging.warn('one time auth fail') self.destroy() + return header_length += ONETIMEAUTH_BYTES self._remote_address = (common.to_str(remote_addr), remote_port) # pause reading From 6ef14e56db71757721e452da5ece0084a092e08a Mon Sep 17 00:00:00 2001 From: smounives Date: Wed, 17 Aug 2016 17:06:54 +0800 Subject: [PATCH 159/196] Add chacha20-ietf crypto (#590) * Add chacha20-ietf crypto * fix chacha20-ietf * PEP8 * Update sodium.py (#1) pep8 --- setup.py | 2 +- shadowsocks/crypto/sodium.py | 23 +++++++++++++++++++++-- tests/chacha20-ietf.json | 10 ++++++++++ tests/libsodium/install.sh | 8 ++++---- 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 tests/chacha20-ietf.json diff --git a/setup.py b/setup.py index e6a7ff7..ba61ad3 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.2", + version="2.9.0", 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/crypto/sodium.py b/shadowsocks/crypto/sodium.py index ae86fef..b744e2c 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -17,7 +17,7 @@ from __future__ import absolute_import, division, print_function, \ with_statement -from ctypes import c_char_p, c_int, c_ulonglong, byref, \ +from ctypes import c_char_p, c_int, c_ulonglong, byref, c_ulong, \ create_string_buffer, c_void_p from shadowsocks.crypto import util @@ -29,7 +29,7 @@ loaded = False buf_size = 2048 -# for salsa20 and chacha20 +# for salsa20 and chacha20 and chacha20-ietf BLOCK_SIZE = 64 @@ -51,6 +51,13 @@ def load_libsodium(): c_ulonglong, c_char_p, c_ulonglong, c_char_p) + libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int + libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, + c_char_p, + c_ulonglong, + c_char_p, + c_ulong, + c_char_p) buf = create_string_buffer(buf_size) loaded = True @@ -68,6 +75,8 @@ class SodiumCrypto(object): self.cipher = libsodium.crypto_stream_salsa20_xor_ic elif cipher_name == 'chacha20': self.cipher = libsodium.crypto_stream_chacha20_xor_ic + elif cipher_name == 'chacha20-ietf': + self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic else: raise Exception('Unknown cipher') # byte counter, not block counter @@ -97,6 +106,7 @@ class SodiumCrypto(object): ciphers = { 'salsa20': (32, 8, SodiumCrypto), 'chacha20': (32, 8, SodiumCrypto), + 'chacha20-ietf': (32, 12, SodiumCrypto), } @@ -115,6 +125,15 @@ def test_chacha20(): util.run_cipher(cipher, decipher) +def test_chacha20_ietf(): + + cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) + decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + if __name__ == '__main__': test_chacha20() test_salsa20() + test_chacha20_ietf() diff --git a/tests/chacha20-ietf.json b/tests/chacha20-ietf.json new file mode 100644 index 0000000..bfe3e1f --- /dev/null +++ b/tests/chacha20-ietf.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"chacha20-ietf", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/libsodium/install.sh b/tests/libsodium/install.sh index b0e35fa..790e471 100755 --- a/tests/libsodium/install.sh +++ b/tests/libsodium/install.sh @@ -1,10 +1,10 @@ #!/bin/bash -if [ ! -d libsodium-1.0.1 ]; then - wget https://github.com/jedisct1/libsodium/releases/download/1.0.1/libsodium-1.0.1.tar.gz || exit 1 - tar xf libsodium-1.0.1.tar.gz || exit 1 +if [ ! -d libsodium-1.0.11 ]; then + wget https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz || exit 1 + tar xf libsodium-1.0.11.tar.gz || exit 1 fi -pushd libsodium-1.0.1 +pushd libsodium-1.0.11 ./configure && make -j2 && make install || exit 1 sudo ldconfig popd From f35590b2e205852be5e3ccdd6eac5df79fb0b5eb Mon Sep 17 00:00:00 2001 From: ficapy Date: Tue, 23 Aug 2016 22:00:32 +0800 Subject: [PATCH 160/196] Dockfile add libsodium (#601) --- Dockerfile | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 36ddaa0..510d422 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,17 @@ -FROM stackbrew/debian:jessie -RUN apt-get update -RUN apt-get install -y python python-setuptools +FROM ubuntu:14.04 -ADD . /shadowsocks +RUN apt-get update && apt-get install -y \ + python-software-properties \ + software-properties-common \ + && add-apt-repository ppa:chris-lea/libsodium \ + && echo "deb http://ppa.launchpad.net/chris-lea/libsodium/ubuntu trusty main" >> /etc/apt/sources.list \ + && echo "deb-src http://ppa.launchpad.net/chris-lea/libsodium/ubuntu trusty main" >> /etc/apt/sources.list \ + && apt-get update \ + && apt-get install -y libsodium-dev python-pip -WORKDIR /shadowsocks -RUN python setup.py install -CMD ssserver +RUN pip install shadowsocks + +ENTRYPOINT ["/usr/local/bin/ssserver"] + +# usage: +# docker run -d --restart=always -p 1314:1314 ficapy/shadowsocks -s 0.0.0.0 -p 1314 -k $PD -m chacha20 From 56bf81f58a09944c4a68a53711329ebe8df2dca3 Mon Sep 17 00:00:00 2001 From: v3aqb Date: Sun, 4 Sep 2016 15:05:15 +0800 Subject: [PATCH 161/196] fix ota (#609)(#610) (#610) --- shadowsocks/tcprelay.py | 20 ++++++++++++-------- shadowsocks/udprelay.py | 14 +++++++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 075a50b..54c364f 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -126,6 +126,7 @@ class TCPRelayHandler(object): self._ota_enable = True else: self._ota_enable = False + self._ota_enable_session = self._ota_enable self._ota_buff_head = b'' self._ota_buff_data = b'' self._ota_len = 0 @@ -247,12 +248,12 @@ class TCPRelayHandler(object): def _handle_stage_connecting(self, data): if self._is_local: - if self._ota_enable: + if self._ota_enable_session: data = self._ota_chunk_data_gen(data) data = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data) else: - if self._ota_enable: + if self._ota_enable_session: self._ota_chunk_data(data, self._data_to_write_to_remote.append) else: @@ -327,8 +328,11 @@ class TCPRelayHandler(object): self._client_address[0], self._client_address[1])) if self._is_local is False: # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._ota_enable or addrtype & ADDRTYPE_AUTH: - self._ota_enable = True + self._ota_enable_session = addrtype & ADDRTYPE_AUTH + if self._ota_enable and not self._ota_enable_session: + logging.warn('client one time auth is required') + return + if self._ota_enable_session: if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('one time auth header is too short') return None @@ -352,7 +356,7 @@ class TCPRelayHandler(object): self._local_sock) # spec https://shadowsocks.org/en/spec/one-time-auth.html # ATYP & 0x10 == 1, then OTA is enabled. - if self._ota_enable: + if self._ota_enable_session: data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] key = self._encryptor.cipher_iv + self._encryptor.key data += onetimeauth_gen(data, key) @@ -362,7 +366,7 @@ class TCPRelayHandler(object): self._dns_resolver.resolve(self._chosen_server[0], self._handle_dns_resolved) else: - if self._ota_enable: + if self._ota_enable_session: data = data[header_length:] self._ota_chunk_data(data, self._data_to_write_to_remote.append) @@ -485,12 +489,12 @@ class TCPRelayHandler(object): def _handle_stage_stream(self, data): if self._is_local: - if self._ota_enable: + if self._ota_enable_session: data = self._ota_chunk_data_gen(data) data = self._encryptor.encrypt(data) self._write_to_sock(data, self._remote_sock) else: - if self._ota_enable: + if self._ota_enable_session: self._ota_chunk_data(data, self._write_to_sock_remote) else: self._write_to_sock(data, self._remote_sock) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 849f611..41ae5d6 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -99,9 +99,10 @@ class UDPRelay(object): self._method = config['method'] self._timeout = config['timeout'] if 'one_time_auth' in config and config['one_time_auth']: - self._one_time_auth_enable = True + self._ota_enable = True else: - self._one_time_auth_enable = False + self._ota_enable = False + self._ota_enable_session = self._ota_enable self._is_local = is_local self._cache = lru_cache.LRUCache(timeout=config['timeout'], close_callback=self._close_client) @@ -183,8 +184,11 @@ class UDPRelay(object): else: server_addr, server_port = dest_addr, dest_port # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._one_time_auth_enable or addrtype & ADDRTYPE_AUTH: - self._one_time_auth_enable = True + self._ota_enable_session = addrtype & ADDRTYPE_AUTH + if self._ota_enable and not self._ota_enable_session: + logging.warn('client one time auth is required') + return + if self._ota_enable_session: if len(data) < header_length + ONETIMEAUTH_BYTES: logging.warn('UDP one time auth header is too short') return @@ -226,7 +230,7 @@ class UDPRelay(object): if self._is_local: key, iv, m = encrypt.gen_key_iv(self._password, self._method) # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._one_time_auth_enable: + if self._ota_enable_session: data = self._ota_chunk_data_gen(key, iv, data) data = encrypt.encrypt_all_m(key, iv, m, self._method, data) if not data: From 5c11527e1b1294d6e5195e3b0bec34276d4bb950 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Sun, 4 Sep 2016 01:18:13 -0700 Subject: [PATCH 162/196] fix ota call write_to_sock multi --- shadowsocks/tcprelay.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 54c364f..810e713 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -449,6 +449,7 @@ class TCPRelayHandler(object): def _ota_chunk_data(self, data, data_cb): # spec https://shadowsocks.org/en/spec/one-time-auth.html + unchunk_data = b'' while len(data) > 0: if self._ota_len == 0: # get DATA.LEN + HMAC-SHA1 @@ -472,11 +473,12 @@ class TCPRelayHandler(object): if onetimeauth_verify(_hash, _data, key) is False: logging.warn('one time auth fail, drop chunk !') else: - data_cb(self._ota_buff_data) + unchunk_data += _data self._ota_chunk_idx += 1 self._ota_buff_head = b'' self._ota_buff_data = b'' self._ota_len = 0 + data_cb(unchunk_data) return def _ota_chunk_data_gen(self, data): From 5cd9f04948c6a46bf3af75577a2225bff9ebc9f1 Mon Sep 17 00:00:00 2001 From: ahxxm Date: Fri, 7 Oct 2016 12:30:17 +0800 Subject: [PATCH 163/196] Refactor (#615) * make tcprelay.py less nested * import traceback module at the top * make loops in DNSResolver less nested * make manager.py less nested * introduce exception_handle decorator make try/except block more clean * apply exception_handle decorator to tcprelay * quote condition judgement * pep8 fix --- shadowsocks/asyncdns.py | 39 +++--- shadowsocks/eventloop.py | 2 +- shadowsocks/local.py | 44 +++--- shadowsocks/manager.py | 26 ++-- shadowsocks/shell.py | 47 +++++++ shadowsocks/tcprelay.py | 293 +++++++++++++++++++-------------------- 6 files changed, 248 insertions(+), 203 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 44528d7..91601ea 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -276,15 +276,18 @@ class DNSResolver(object): content = f.readlines() for line in content: line = line.strip() - if line: - if line.startswith(b'nameserver'): - parts = line.split() - if len(parts) >= 2: - server = parts[1] - if common.is_ip(server) == socket.AF_INET: - if type(server) != str: - server = server.decode('utf8') - self._servers.append(server) + if not (line and line.startswith(b'nameserver')): + continue + + parts = line.split() + if len(parts) < 2: + continue + + server = parts[1] + if common.is_ip(server) == socket.AF_INET: + if type(server) != str: + server = server.decode('utf8') + self._servers.append(server) except IOError: pass if not self._servers: @@ -299,13 +302,17 @@ class DNSResolver(object): for line in f.readlines(): line = line.strip() parts = line.split() - if len(parts) >= 2: - ip = parts[0] - if common.is_ip(ip): - for i in range(1, len(parts)): - hostname = parts[i] - if hostname: - self._hosts[hostname] = ip + if len(parts) < 2: + continue + + ip = parts[0] + if not common.is_ip(ip): + continue + + for i in range(1, len(parts)): + hostname = parts[i] + if hostname: + self._hosts[hostname] = ip except IOError: self._hosts['localhost'] = '127.0.0.1' diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 78b532c..ce5da37 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -25,6 +25,7 @@ import os import time import socket import select +import traceback import errno import logging from collections import defaultdict @@ -204,7 +205,6 @@ class EventLoop(object): logging.debug('poll:%s', e) else: logging.error('poll:%s', e) - import traceback traceback.print_exc() continue diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 4255a2e..dfc8032 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -27,6 +27,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns +@shell.exception_handle(self_=False, exit_code=1) def main(): shell.check_python() @@ -37,36 +38,31 @@ def main(): os.chdir(p) config = shell.get_config(True) - daemon.daemon_exec(config) - try: - logging.info("starting local at %s:%d" % - (config['local_address'], config['local_port'])) + logging.info("starting local at %s:%d" % + (config['local_address'], config['local_port'])) - dns_resolver = asyncdns.DNSResolver() - tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) - udp_server = udprelay.UDPRelay(config, dns_resolver, True) - loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop) - tcp_server.add_to_loop(loop) - udp_server.add_to_loop(loop) + dns_resolver = asyncdns.DNSResolver() + tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) + udp_server = udprelay.UDPRelay(config, dns_resolver, True) + loop = eventloop.EventLoop() + dns_resolver.add_to_loop(loop) + tcp_server.add_to_loop(loop) + udp_server.add_to_loop(loop) - def handler(signum, _): - logging.warn('received SIGQUIT, doing graceful shutting down..') - tcp_server.close(next_tick=True) - udp_server.close(next_tick=True) - signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) + def handler(signum, _): + logging.warn('received SIGQUIT, doing graceful shutting down..') + tcp_server.close(next_tick=True) + udp_server.close(next_tick=True) + signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) - def int_handler(signum, _): - sys.exit(1) - signal.signal(signal.SIGINT, int_handler) - - daemon.set_user(config.get('user', None)) - loop.run() - except Exception as e: - shell.print_exception(e) + def int_handler(signum, _): sys.exit(1) + signal.signal(signal.SIGINT, int_handler) + + daemon.set_user(config.get('user', None)) + loop.run() if __name__ == '__main__': main() diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index b42ffa9..bba542e 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -173,18 +173,20 @@ class Manager(object): self._statistics.clear() 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() + if not self._control_client_addr: + return + + 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() diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 6cca837..041086a 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -23,6 +23,10 @@ import json import sys import getopt import logging +import traceback + +from functools import wraps + from shadowsocks.common import to_bytes, to_str, IPNetwork from shadowsocks import encrypt @@ -53,6 +57,49 @@ def print_exception(e): traceback.print_exc() +def exception_handle(self_, err_msg=None, exit_code=None, + destroy=False, conn_err=False): + # self_: if function passes self as first arg + + def process_exception(e, self=None): + print_exception(e) + if err_msg: + logging.error(err_msg) + if exit_code: + sys.exit(1) + + if not self_: + return + + if conn_err: + addr, port = self._client_address[0], self._client_address[1] + logging.error('%s when handling connection from %s:%d' % + (e, addr, port)) + if self._config['verbose']: + traceback.print_exc() + if destroy: + self.destroy() + + def decorator(func): + if self_: + @wraps(func) + def wrapper(self, *args, **kwargs): + try: + func(self, *args, **kwargs) + except Exception as e: + process_exception(e, self) + else: + @wraps(func) + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except Exception as e: + process_exception(e) + + return wrapper + return decorator + + def print_shadowsocks(): version = '' try: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 810e713..2e4772d 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -190,21 +190,23 @@ class TCPRelayHandler(object): if self._upstream_status != status: self._upstream_status = status dirty = True - if dirty: - if self._local_sock: - event = eventloop.POLL_ERR - if self._downstream_status & WAIT_STATUS_WRITING: - event |= eventloop.POLL_OUT - if self._upstream_status & WAIT_STATUS_READING: - event |= eventloop.POLL_IN - 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) + if not dirty: + return + + if self._local_sock: + event = eventloop.POLL_ERR + if self._downstream_status & WAIT_STATUS_WRITING: + event |= eventloop.POLL_OUT + if self._upstream_status & WAIT_STATUS_READING: + event |= eventloop.POLL_IN + 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) def _write_to_sock(self, data, sock): # write data to sock @@ -247,19 +249,20 @@ class TCPRelayHandler(object): return True def _handle_stage_connecting(self, data): - if self._is_local: - if self._ota_enable_session: - data = self._ota_chunk_data_gen(data) - data = self._encryptor.encrypt(data) - self._data_to_write_to_remote.append(data) - else: + if not self._is_local: if self._ota_enable_session: self._ota_chunk_data(data, self._data_to_write_to_remote.append) else: self._data_to_write_to_remote.append(data) - if self._is_local and not self._fastopen_connected and \ - self._config['fast_open']: + return + + if self._ota_enable_session: + data = self._ota_chunk_data_gen(data) + data = self._encryptor.encrypt(data) + self._data_to_write_to_remote.append(data) + + if self._config['fast_open'] and not self._fastopen_connected: # for sslocal and fastopen, we basically wait for data and use # sendto to connect try: @@ -293,93 +296,88 @@ class TCPRelayHandler(object): traceback.print_exc() self.destroy() + @shell.exception_handle(self_=True, destroy=True, conn_err=True) def _handle_stage_addr(self, data): - try: - if self._is_local: - cmd = common.ord(data[1]) - if cmd == CMD_UDP_ASSOCIATE: - logging.debug('UDP associate') - if self._local_sock.family == socket.AF_INET6: - header = b'\x05\x00\x00\x04' - else: - header = b'\x05\x00\x00\x01' - addr, port = self._local_sock.getsockname()[:2] - addr_to_send = socket.inet_pton(self._local_sock.family, - addr) - port_to_send = struct.pack('>H', port) - self._write_to_sock(header + addr_to_send + port_to_send, - self._local_sock) - self._stage = STAGE_UDP_ASSOC - # just wait for the client to disconnect - return - elif cmd == CMD_CONNECT: - # just trim VER CMD RSV - data = data[3:] + if self._is_local: + cmd = common.ord(data[1]) + if cmd == CMD_UDP_ASSOCIATE: + logging.debug('UDP associate') + if self._local_sock.family == socket.AF_INET6: + header = b'\x05\x00\x00\x04' else: - logging.error('unknown command %d', cmd) + header = b'\x05\x00\x00\x01' + addr, port = self._local_sock.getsockname()[:2] + addr_to_send = socket.inet_pton(self._local_sock.family, + addr) + port_to_send = struct.pack('>H', port) + self._write_to_sock(header + addr_to_send + port_to_send, + self._local_sock) + self._stage = STAGE_UDP_ASSOC + # just wait for the client to disconnect + return + elif cmd == CMD_CONNECT: + # just trim VER CMD RSV + data = data[3:] + else: + logging.error('unknown command %d', cmd) + self.destroy() + return + header_result = parse_header(data) + if header_result is None: + raise Exception('can not parse header') + addrtype, remote_addr, remote_port, header_length = header_result + logging.info('connecting %s:%d from %s:%d' % + (common.to_str(remote_addr), remote_port, + self._client_address[0], self._client_address[1])) + if self._is_local is False: + # spec https://shadowsocks.org/en/spec/one-time-auth.html + self._ota_enable_session = addrtype & ADDRTYPE_AUTH + if self._ota_enable and not self._ota_enable_session: + logging.warn('client one time auth is required') + return + if self._ota_enable_session: + if len(data) < header_length + ONETIMEAUTH_BYTES: + logging.warn('one time auth header is too short') + return None + offset = header_length + ONETIMEAUTH_BYTES + _hash = data[header_length: offset] + _data = data[:header_length] + key = self._encryptor.decipher_iv + self._encryptor.key + if onetimeauth_verify(_hash, _data, key) is False: + logging.warn('one time auth fail') self.destroy() return - header_result = parse_header(data) - if header_result is None: - raise Exception('can not parse header') - addrtype, remote_addr, remote_port, header_length = header_result - logging.info('connecting %s:%d from %s:%d' % - (common.to_str(remote_addr), remote_port, - self._client_address[0], self._client_address[1])) - if self._is_local is False: - # spec https://shadowsocks.org/en/spec/one-time-auth.html - self._ota_enable_session = addrtype & ADDRTYPE_AUTH - if self._ota_enable and not self._ota_enable_session: - logging.warn('client one time auth is required') - return - if self._ota_enable_session: - if len(data) < header_length + ONETIMEAUTH_BYTES: - logging.warn('one time auth header is too short') - return None - offset = header_length + ONETIMEAUTH_BYTES - _hash = data[header_length: offset] - _data = data[:header_length] - key = self._encryptor.decipher_iv + self._encryptor.key - if onetimeauth_verify(_hash, _data, key) is False: - logging.warn('one time auth fail') - self.destroy() - return - header_length += ONETIMEAUTH_BYTES - self._remote_address = (common.to_str(remote_addr), remote_port) - # pause reading - self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) - self._stage = STAGE_DNS - if self._is_local: - # forward address to remote - self._write_to_sock((b'\x05\x00\x00\x01' - b'\x00\x00\x00\x00\x10\x10'), - self._local_sock) - # spec https://shadowsocks.org/en/spec/one-time-auth.html - # ATYP & 0x10 == 1, then OTA is enabled. - if self._ota_enable_session: - data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] - key = self._encryptor.cipher_iv + self._encryptor.key - data += onetimeauth_gen(data, key) - data_to_send = self._encryptor.encrypt(data) - self._data_to_write_to_remote.append(data_to_send) - # notice here may go into _handle_dns_resolved directly - self._dns_resolver.resolve(self._chosen_server[0], - self._handle_dns_resolved) - else: - if self._ota_enable_session: - data = data[header_length:] - self._ota_chunk_data(data, - self._data_to_write_to_remote.append) - elif len(data) > header_length: - self._data_to_write_to_remote.append(data[header_length:]) - # notice here may go into _handle_dns_resolved directly - self._dns_resolver.resolve(remote_addr, - self._handle_dns_resolved) - except Exception as e: - self._log_error(e) - if self._config['verbose']: - traceback.print_exc() - self.destroy() + header_length += ONETIMEAUTH_BYTES + self._remote_address = (common.to_str(remote_addr), remote_port) + # pause reading + self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) + self._stage = STAGE_DNS + if self._is_local: + # forward address to remote + self._write_to_sock((b'\x05\x00\x00\x01' + b'\x00\x00\x00\x00\x10\x10'), + self._local_sock) + # spec https://shadowsocks.org/en/spec/one-time-auth.html + # ATYP & 0x10 == 1, then OTA is enabled. + if self._ota_enable_session: + data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] + key = self._encryptor.cipher_iv + self._encryptor.key + data += onetimeauth_gen(data, key) + data_to_send = self._encryptor.encrypt(data) + self._data_to_write_to_remote.append(data_to_send) + # notice here may go into _handle_dns_resolved directly + self._dns_resolver.resolve(self._chosen_server[0], + self._handle_dns_resolved) + else: + if self._ota_enable_session: + data = data[header_length:] + self._ota_chunk_data(data, + self._data_to_write_to_remote.append) + elif len(data) > header_length: + self._data_to_write_to_remote.append(data[header_length:]) + # notice here may go into _handle_dns_resolved directly + self._dns_resolver.resolve(remote_addr, + self._handle_dns_resolved) def _create_remote_socket(self, ip, port): addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, @@ -398,51 +396,50 @@ class TCPRelayHandler(object): remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) return remote_sock + @shell.exception_handle(self_=True) def _handle_dns_resolved(self, result, error): if error: - self._log_error(error) + addr, port = self._client_address[0], self._client_address[1] + logging.error('%s when handling connection from %s:%d' % + (error, addr, port)) + self.destroy() + return + if not (result and result[1]): self.destroy() return - if result and result[1]: - ip = result[1] - try: - self._stage = STAGE_CONNECTING - remote_addr = ip - if self._is_local: - remote_port = self._chosen_server[1] - else: - remote_port = self._remote_address[1] - if self._is_local and self._config['fast_open']: - # for fastopen: - # wait for more data arrive and send them in one SYN - self._stage = STAGE_CONNECTING - # we don't have to wait for remote since it's not - # created - self._update_stream(STREAM_UP, WAIT_STATUS_READING) - # TODO when there is already data in this packet - else: - # else do connect - remote_sock = self._create_remote_socket(remote_addr, - remote_port) - try: - remote_sock.connect((remote_addr, remote_port)) - except (OSError, IOError) as e: - if eventloop.errno_from_exception(e) == \ - errno.EINPROGRESS: - pass - self._loop.add(remote_sock, - 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) - return - except Exception as e: - shell.print_exception(e) - if self._config['verbose']: - traceback.print_exc() - self.destroy() + ip = result[1] + self._stage = STAGE_CONNECTING + remote_addr = ip + if self._is_local: + remote_port = self._chosen_server[1] + else: + remote_port = self._remote_address[1] + + if self._is_local and self._config['fast_open']: + # for fastopen: + # wait for more data arrive and send them in one SYN + self._stage = STAGE_CONNECTING + # we don't have to wait for remote since it's not + # created + self._update_stream(STREAM_UP, WAIT_STATUS_READING) + # TODO when there is already data in this packet + else: + # else do connect + remote_sock = self._create_remote_socket(remote_addr, + remote_port) + try: + remote_sock.connect((remote_addr, remote_port)) + except (OSError, IOError) as e: + if eventloop.errno_from_exception(e) == \ + errno.EINPROGRESS: + pass + self._loop.add(remote_sock, + 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) def _write_to_sock_remote(self, data): self._write_to_sock(data, self._remote_sock) @@ -661,10 +658,6 @@ class TCPRelayHandler(object): else: logging.warn('unknown socket') - def _log_error(self, e): - logging.error('%s when handling connection from %s:%d' % - (e, self._client_address[0], self._client_address[1])) - def destroy(self): # destroy the handler and release any resources # promises: From 4f28f7c8e409a06709bca4d8a805f6e6726fc089 Mon Sep 17 00:00:00 2001 From: loggerhead Date: Mon, 10 Oct 2016 23:05:20 +0800 Subject: [PATCH 164/196] Fix a OTA bug on `_ota_chunk_data` (#642) * fix a OTA bug * correct a wrong comment --- shadowsocks/tcprelay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 2e4772d..2ff7b21 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -358,7 +358,7 @@ class TCPRelayHandler(object): b'\x00\x00\x00\x00\x10\x10'), self._local_sock) # spec https://shadowsocks.org/en/spec/one-time-auth.html - # ATYP & 0x10 == 1, then OTA is enabled. + # ATYP & 0x10 == 0x10, then OTA is enabled. if self._ota_enable_session: data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] key = self._encryptor.cipher_iv + self._encryptor.key @@ -458,7 +458,7 @@ class TCPRelayHandler(object): return data_len = self._ota_buff_head[:ONETIMEAUTH_CHUNK_DATA_LEN] self._ota_len = struct.unpack('>H', data_len)[0] - length = min(self._ota_len, len(data)) + length = min(self._ota_len - len(self._ota_buff_data), len(data)) self._ota_buff_data += data[:length] data = data[length:] if len(self._ota_buff_data) == self._ota_len: From 5a05312189a4e94b454d225895b5e1709c1b9fc3 Mon Sep 17 00:00:00 2001 From: Shell Chen Date: Wed, 12 Oct 2016 00:31:41 +1100 Subject: [PATCH 165/196] Fix method on manager's add command (#578) (#614) --- shadowsocks/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index bba542e..f3a7b77 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -141,6 +141,8 @@ class Manager(object): command, config_json = parts try: config = shell.parse_json_in_str(config_json) + if 'method' in config: + config['method'] = common.to_str(config['method']) return command, config except Exception as e: logging.error(e) From 6dae6e2c514134c139abbaee54ca74033738f8de Mon Sep 17 00:00:00 2001 From: zz Date: Sun, 20 Nov 2016 14:57:52 +0800 Subject: [PATCH 166/196] fix that autoban can not get ip when use ipv6 (#674) --- utils/autoban.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/autoban.py b/utils/autoban.py index 1bbb65c..c7af0a5 100755 --- a/utils/autoban.py +++ b/utils/autoban.py @@ -38,7 +38,7 @@ if __name__ == '__main__': banned = set() for line in sys.stdin: if 'can not parse header when' in line: - ip = line.split()[-1].split(':')[0] + ip = line.split()[-1].split(':')[-2] if ip not in ips: ips[ip] = 1 print(ip) From 72f1d68a05a43024d678f7d3d71b64563a4473d6 Mon Sep 17 00:00:00 2001 From: loggerhead Date: Sun, 20 Nov 2016 14:59:32 +0800 Subject: [PATCH 167/196] Fixed #675 (#676) * fix a OTA bug * correct a wrong comment * ignore emacs autosave files * keep consistence with the defensive style * a little refractor * fix daemon stop failed (#675) * fix test failed --- .gitignore | 3 +++ shadowsocks/shell.py | 31 ++++++++++++++++--------------- shadowsocks/tcprelay.py | 14 +++++--------- shadowsocks/udprelay.py | 11 ++--------- tests/test_command.sh | 4 ++-- 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 6c1b61e..4907974 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ htmlcov .DS_Store .idea + +#Emacs +.#* diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 041086a..3c6676f 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -125,6 +125,22 @@ def check_config(config, is_local): # no need to specify configuration for daemon stop return + if is_local: + if config.get('server', None) is None: + logging.error('server addr not specified') + print_local_help() + sys.exit(2) + else: + config['server'] = to_str(config['server']) + else: + 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')) + except Exception as e: + logging.error(e) + sys.exit(2) + if is_local and not config.get('password', None): logging.error('password not specified') print_help(is_local) @@ -280,21 +296,6 @@ def get_config(is_local): config['local_port'] = config.get('local_port', 1080) config['one_time_auth'] = config.get('one_time_auth', False) config['prefer_ipv6'] = config.get('prefer_ipv6', False) - if is_local: - if config.get('server', None) is None: - logging.error('server addr not specified') - print_local_help() - sys.exit(2) - else: - config['server'] = to_str(config['server']) - else: - 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')) - except Exception as e: - logging.error(e) - sys.exit(2) config['server_port'] = config.get('server_port', 8388) logging.getLogger('').handlers = [] diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 2ff7b21..207407a 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -122,10 +122,7 @@ class TCPRelayHandler(object): self._stage = STAGE_INIT self._encryptor = encrypt.Encryptor(config['password'], config['method']) - if 'one_time_auth' in config and config['one_time_auth']: - self._ota_enable = True - else: - self._ota_enable = False + self._ota_enable = config.get('one_time_auth', False) self._ota_enable_session = self._ota_enable self._ota_buff_head = b'' self._ota_buff_data = b'' @@ -138,10 +135,7 @@ class TCPRelayHandler(object): self._downstream_status = WAIT_STATUS_INIT self._client_address = local_sock.getpeername()[:2] self._remote_address = None - if 'forbidden_ip' in config: - self._forbidden_iplist = config['forbidden_ip'] - else: - self._forbidden_iplist = None + self._forbidden_iplist = config.get('forbidden_ip') if is_local: self._chosen_server = self._get_a_server() fd_to_handlers[local_sock.fileno()] = self @@ -362,7 +356,9 @@ class TCPRelayHandler(object): if self._ota_enable_session: data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] key = self._encryptor.cipher_iv + self._encryptor.key - data += onetimeauth_gen(data, key) + _header = data[:header_length] + sha110 = onetimeauth_gen(data, key) + data = _header + sha110 + data[header_length:] data_to_send = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data_to_send) # notice here may go into _handle_dns_resolved directly diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 41ae5d6..3a36cff 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -98,10 +98,7 @@ class UDPRelay(object): self._password = common.to_bytes(config['password']) self._method = config['method'] self._timeout = config['timeout'] - if 'one_time_auth' in config and config['one_time_auth']: - self._ota_enable = True - else: - self._ota_enable = False + self._ota_enable = config.get('one_time_auth', False) self._ota_enable_session = self._ota_enable self._is_local = is_local self._cache = lru_cache.LRUCache(timeout=config['timeout'], @@ -112,11 +109,7 @@ class UDPRelay(object): self._eventloop = None self._closed = False self._sockets = set() - if 'forbidden_ip' in config: - self._forbidden_iplist = config['forbidden_ip'] - else: - self._forbidden_iplist = None - + self._forbidden_iplist = config.get('forbidden_ip') addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if len(addrs) == 0: diff --git a/tests/test_command.sh b/tests/test_command.sh index 8225740..9af91c3 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -30,7 +30,7 @@ $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 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!" $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 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified" +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" @@ -39,7 +39,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" +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 229ec75e4d091050b54bab9bea57fe7b4ad1cec5 Mon Sep 17 00:00:00 2001 From: Yuan Alvin Fu Date: Sun, 20 Nov 2016 02:22:17 -0500 Subject: [PATCH 168/196] Update README.md (#643) --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index fb1ffca..ee4b880 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,15 @@ To check the log: Check all the options via `-h`. You can also use a [Configuration] file instead. +### Usage with Config File + +[Create configeration file and run](https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File) + +To start: + + ssserver -c /etc/shadowsocks.json + + Documentation ------------- From d31003e9bf031dedaf8caef1daaee29be41f10a2 Mon Sep 17 00:00:00 2001 From: "Shell.Xu" Date: Sun, 20 Nov 2016 15:29:11 +0800 Subject: [PATCH 169/196] new debian package (#659) * * new upstream version * change repository. * change license. * standards version change to 3.9.8 * fix #810688: man page references texinfo documents which don't exist * * merge back thomas's change * * Fix compatible issue (Closes: #845016) --- debian/changelog | 23 +++++++++++++++++++ debian/control | 21 ++++++++++------- debian/copyright | 43 ++++++++++++++++------------------- debian/install | 2 +- debian/shadowsocks.manpages | 2 +- debian/sslocal.1 | 4 ++-- debian/ssserver.1 | 4 ++-- shadowsocks/crypto/openssl.py | 11 ++++++--- 8 files changed, 70 insertions(+), 40 deletions(-) diff --git a/debian/changelog b/debian/changelog index 4e7ad16..2bf239c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,26 @@ +shadowsocks (2.9.0-2) unstable; urgency=medium + + [ Shell.Xu ] + * Fix compatible issue (Closes: #845016) + + -- Shell.Xu Sun, 20 Nov 2016 14:33:31 +0800 + +shadowsocks (2.9.0-1) unstable; urgency=medium + + [ Shell Xu ] + * Upstream update (Closes: #824640) + * Remove reference not exists (Closes: #810688) + + [ Thomas Goirand ] + * Added lsb-base as Depends:. + * Removed Pre-Depends: dpkg (>= 1.15.6~). + * Ran wrap-and-sort -t -a. + * Fixed VCS URLs to use https. + * Removed useless obsolete version of python-all build-depends. + * Fixed debian/copyright ordering. + + -- Shell.Xu Sat, 01 Oct 2016 16:14:47 +0800 + shadowsocks (2.1.0-1) unstable; urgency=low * Initial release (Closes: #758900) diff --git a/debian/control b/debian/control index da00920..fa0b1ec 100644 --- a/debian/control +++ b/debian/control @@ -2,18 +2,23 @@ Source: shadowsocks Section: python Priority: extra Maintainer: Shell.Xu -Build-Depends: debhelper (>= 8), python-all (>= 2.6.6-3~), python-setuptools -Standards-Version: 3.9.5 -Homepage: https://github.com/clowwindy/shadowsocks -Vcs-Git: git://github.com/shell909090/shadowsocks.git -Vcs-Browser: http://github.com/shell909090/shadowsocks +Build-Depends: debhelper (>= 8), + python-all, + python-setuptools, +Standards-Version: 3.9.8 +Homepage: https://github.com/shadowsocks/shadowsocks +Vcs-Git: https://github.com/shell909090/shadowsocks.git +Vcs-Browser: https://github.com/shell909090/shadowsocks Package: shadowsocks Architecture: all -Pre-Depends: dpkg (>= 1.15.6~) -Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources, python-m2crypto +Depends: lsb-base (>= 3.0-6), + python-m2crypto, + python-pkg-resources, + ${misc:Depends}, + ${python:Depends}, Description: Fast tunnel proxy that helps you bypass firewalls A secure socks5 proxy, designed to protect your Internet traffic. . This package contain local and server part of shadowsocks, a fast, - powerful tunnel proxy to bypass firewalls. \ No newline at end of file + powerful tunnel proxy to bypass firewalls. diff --git a/debian/copyright b/debian/copyright index 7be8162..14f0851 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,30 +1,27 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: shadowsocks -Source: https://github.com/clowwindy/shadowsocks - -Files: debian/* -Copyright: 2014 Shell.Xu -License: Expat +Source: https://github.com/shadowsocks/shadowsocks Files: * Copyright: 2014 clowwindy -License: Expat +License: Apache-2.0 -License: Expat - 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: - . - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. +Files: debian/* +Copyright: 2016 Shell.Xu +License: Apache-2.0 + +License: Apache-2.0 + 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. + . + On Debian systems, the complete text of the Apache License 2.0 can + be found in "/usr/share/common-licenses/Apache-2.0" diff --git a/debian/install b/debian/install index a614864..a16a450 100644 --- a/debian/install +++ b/debian/install @@ -1 +1 @@ -debian/config.json etc/shadowsocks/ \ No newline at end of file +debian/config.json etc/shadowsocks/ diff --git a/debian/shadowsocks.manpages b/debian/shadowsocks.manpages index 3df8a33..98b6847 100644 --- a/debian/shadowsocks.manpages +++ b/debian/shadowsocks.manpages @@ -1,2 +1,2 @@ debian/sslocal.1 -debian/ssserver.1 \ No newline at end of file +debian/ssserver.1 diff --git a/debian/sslocal.1 b/debian/sslocal.1 index 0c2cf51..05ef510 100644 --- a/debian/sslocal.1 +++ b/debian/sslocal.1 @@ -55,5 +55,5 @@ Quiet mode, only show warnings/errors. The programs are documented fully by .IR "Shell Xu " and -.IR "Clowwindy ", -available via the Info system. +.IR "Clowwindy " +. diff --git a/debian/ssserver.1 b/debian/ssserver.1 index 0c2cf51..05ef510 100644 --- a/debian/ssserver.1 +++ b/debian/ssserver.1 @@ -55,5 +55,5 @@ Quiet mode, only show warnings/errors. The programs are documented fully by .IR "Shell Xu " and -.IR "Clowwindy ", -available via the Info system. +.IR "Clowwindy " +. diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index 3775b6c..da7f177 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -32,7 +32,7 @@ buf_size = 2048 def load_openssl(): - global loaded, libcrypto, buf + global loaded, libcrypto, buf, ctx_cleanup libcrypto = util.find_library(('crypto', 'eay32'), 'EVP_get_cipherbyname', @@ -49,7 +49,12 @@ def load_openssl(): libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, c_char_p, c_int) - libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) + try: + libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) + ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup + except AttributeError: + libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,) + ctx_cleanup = libcrypto.EVP_CIPHER_CTX_reset libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): libcrypto.OpenSSL_add_all_ciphers() @@ -108,7 +113,7 @@ class OpenSSLCrypto(object): def clean(self): if self._ctx: - libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) + ctx_cleanup(self._ctx) libcrypto.EVP_CIPHER_CTX_free(self._ctx) From bdefeb523f247df512a33088203fcd2804a0f641 Mon Sep 17 00:00:00 2001 From: Anthony Wong Date: Mon, 2 Jan 2017 14:56:25 +0800 Subject: [PATCH 170/196] Update README.md (#697) Add missing links. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee4b880..5c7fa39 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ CentOS: Windows: -See [Install Server on Windows] +See [Install Shadowsocks Server on Windows](https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows). ### Usage @@ -64,7 +64,7 @@ To start: Documentation ------------- -You can find all the documentation in the [Wiki]. +You can find all the documentation in the [Wiki](https://github.com/shadowsocks/shadowsocks/wiki). License ------- From 392e54e5086ce8b3617f6aab8b7e54fe516a0c41 Mon Sep 17 00:00:00 2001 From: Lution Date: Mon, 2 Jan 2017 14:56:57 +0800 Subject: [PATCH 171/196] update regex to support hostnames containing underscore (#694) --- shadowsocks/asyncdns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 91601ea..fa5be41 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -29,7 +29,7 @@ from shadowsocks import common, lru_cache, eventloop, shell CACHE_SWEEP_INTERVAL = 30 -VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d-]{1,63}(? Date: Sun, 1 Jan 2017 23:08:59 -0800 Subject: [PATCH 172/196] 2.9.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ba61ad3..b27c8eb 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.9.0", + version="2.9.1", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 8ac72b0b25920b72042e8ea7a769c5150c463765 Mon Sep 17 00:00:00 2001 From: hdl Date: Tue, 28 Feb 2017 06:15:47 -0800 Subject: [PATCH 173/196] parse dns_servre in config (#739) thx --- shadowsocks/shell.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 3c6676f..1b65594 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -184,6 +184,8 @@ def check_config(config, is_local): if os.name != 'posix': logging.error('user can be used only on Unix') sys.exit(1) + if config.get('dns_server', None) is not None: + logging.info('Specified DNS server: %s' % config['dns_server']) encrypt.try_cipher(config['password'], config['method']) @@ -297,6 +299,7 @@ def get_config(is_local): config['one_time_auth'] = config.get('one_time_auth', False) config['prefer_ipv6'] = config.get('prefer_ipv6', False) config['server_port'] = config.get('server_port', 8388) + config['dns_server'] = config.get('dns_server', None) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') From fe13c20dc1d1e5ce3e97aeecdce17855def39612 Mon Sep 17 00:00:00 2001 From: ImPerat0R_ Date: Tue, 28 Feb 2017 22:19:36 +0800 Subject: [PATCH 174/196] Correct spelling. (#755) thx --- shadowsocks/encrypt.py | 4 ++-- shadowsocks/udprelay.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index ece72ec..a86058b 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -153,7 +153,7 @@ def encrypt_all_m(key, iv, m, method, data): return b''.join(result) -def dencrypt_all(password, method, data): +def decrypt_all(password, method, data): result = [] method = method.lower() (key_len, iv_len, m) = method_supported[method] @@ -228,7 +228,7 @@ def test_encrypt_all_m(): logging.warn(method) key, iv, m = gen_key_iv(b'key', method) cipher = encrypt_all_m(key, iv, m, method, plain) - plain2, key, iv = dencrypt_all(b'key', method, cipher) + plain2, key, iv = decrypt_all(b'key', method, cipher) assert plain == plain2 diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 3a36cff..ba8299b 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -158,9 +158,9 @@ class UDPRelay(object): else: data = data[3:] else: - data, key, iv = encrypt.dencrypt_all(self._password, - self._method, - data) + data, key, iv = encrypt.decrypt_all(self._password, + self._method, + data) # decrypt data if not data: logging.debug( From e06819c124d025c1a63d61c20ad37dbbf63cc534 Mon Sep 17 00:00:00 2001 From: Falseen Date: Thu, 2 Mar 2017 21:20:04 +0800 Subject: [PATCH 175/196] add simple ss-tunnel to shadowsocks for dns forward (#759) * add vscode to .gitignore * add config.json to gitignore * add simple ss-tunnel to shadowsocks for dns forward 1.add tunnel.py file to shadowoscks for dns forward (seem ss-tunnel of ss-libev) 2.add add_header to common.py for add socks5 request header 3.add dns_service dns_server dns_server_port dns_server_port dns_local_port to shell.py and config.json 4.update to udprelay for ss-tunnel (dns forward) 5.update to local.py for ss-tunnel * add config.json.example to shadowsocks * add tunnel_udp_server.close to local.py and tunnel.py * fix error about pep8 and pyflakes for travis * update rename rename add "tunnel_" to dns_service dns_server dns_server_port dns_local_port * fix tunnel for run tunnel alone * fix pep8 for travis * update the config name for tunnel ord name: tunnel_service tunnel_dns_server tunnel_dns_server_port tunnel_dns_local_port new name: both_tunnel_local tunnel_remote tunnel_remote_port tunnel_port * update for tunnel update to local.py shell.py tunnel.py for tunnel * update pep8 for travis * update config to _config for tunnel and fix pep8 * changed "add socks5 header to data" to "add ss header to data" and changed "remove socks5 header" to "remove ss header" * add tcp forward to tunnel and fix a bug for pack_addr from common.py 1. update tunnel to tcprelay.py 2. add tunnel_tcp_server to tunnel.py 3. add tunnel_tcp_server to local.py 4. add `address = to_bytes(address)` to `pack_addr` from common.py (fix a error when address is a domain) * fix pep8 for travis again * remove ss_header from tcprelay.py and update the "header_length" to udprelay.py 1. Remove unnecessary "add ss_header" from tcprelay.py 2. update "data[7:]" to "data[header_length:]" * remove "both_tunnel_local" and fix some error for tunnel * update * update add_header * rename is_tunnel to _is_tunnel https://github.com/shadowsocks/shadowsocks/pull/759 --- .gitignore | 7 ++++ config.json.example | 13 +++++++ shadowsocks/common.py | 8 +++++ shadowsocks/shell.py | 18 ++++++++++ shadowsocks/tcprelay.py | 76 ++++++++++++++++++++++++++--------------- shadowsocks/tunnel.py | 74 +++++++++++++++++++++++++++++++++++++++ shadowsocks/udprelay.py | 33 ++++++++++++++---- 7 files changed, 194 insertions(+), 35 deletions(-) create mode 100644 config.json.example create mode 100644 shadowsocks/tunnel.py diff --git a/.gitignore b/.gitignore index 4907974..0b36d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,10 @@ htmlcov #Emacs .#* + +#vscode +.idea +.vscode + +#ss +config.json diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..4006656 --- /dev/null +++ b/config.json.example @@ -0,0 +1,13 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1080, + "password":"password", + "timeout":600, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false, + "tunnel_remote":"8.8.8.8", + "tunnel_remote_port":53, + "tunnel_port":53 +} diff --git a/shadowsocks/common.py b/shadowsocks/common.py index ee14995..1a58457 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -146,6 +146,7 @@ ADDRTYPE_MASK = 0xF def pack_addr(address): address_str = to_str(address) + address = to_bytes(address) for family in (socket.AF_INET, socket.AF_INET6): try: r = socket.inet_pton(family, address_str) @@ -160,6 +161,13 @@ def pack_addr(address): return b'\x03' + chr(len(address)) + address +# add ss header +def add_header(address, port, data=b''): + _data = b'' + _data = pack_addr(address) + struct.pack('>H', port) + data + return _data + + def parse_header(data): addrtype = ord(data[0]) dest_addr = None diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 1b65594..b46510c 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -132,6 +132,13 @@ def check_config(config, is_local): sys.exit(2) else: config['server'] = to_str(config['server']) + + if config.get('tunnel_remote', None) is None: + logging.error('tunnel_remote addr not specified') + print_local_help() + sys.exit(2) + else: + config['tunnel_remote'] = to_str(config['tunnel_remote']) else: config['server'] = to_str(config.get('server', '0.0.0.0')) try: @@ -159,6 +166,12 @@ def check_config(config, is_local): if 'server_port' in config and type(config['server_port']) != list: config['server_port'] = int(config['server_port']) + if 'tunnel_remote_port' in config: + config['tunnel_remote_port'] = \ + int(config['tunnel_remote_port']) + if 'tunnel_port' in config: + config['tunnel_port'] = int(config['tunnel_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 ['127.0.0.1', 'localhost']: @@ -299,6 +312,11 @@ def get_config(is_local): config['one_time_auth'] = config.get('one_time_auth', False) config['prefer_ipv6'] = config.get('prefer_ipv6', False) config['server_port'] = config.get('server_port', 8388) + + config['tunnel_remote'] = \ + to_str(config.get('tunnel_remote', '8.8.8.8')) + config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53) + config['tunnel_port'] = config.get('tunnel_port', 53) config['dns_server'] = config.get('dns_server', None) logging.getLogger('').handlers = [] diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 207407a..a61cba7 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -106,6 +106,7 @@ class NoAcceptableMethods(Exception): class TCPRelayHandler(object): + def __init__(self, server, fd_to_handlers, loop, local_sock, config, dns_resolver, is_local): self._server = server @@ -115,6 +116,10 @@ class TCPRelayHandler(object): self._remote_sock = None self._config = config self._dns_resolver = dns_resolver + self.tunnel_remote = config.get('tunnel_remote', "8.8.8.8") + self.tunnel_remote_port = config.get('tunnel_remote_port', 53) + self.tunnel_port = config.get('tunnel_port', 53) + self._is_tunnel = server._is_tunnel # TCP Relay works as either sslocal or ssserver # if is_local, this is sslocal @@ -250,7 +255,6 @@ class TCPRelayHandler(object): else: self._data_to_write_to_remote.append(data) return - if self._ota_enable_session: data = self._ota_chunk_data_gen(data) data = self._encryptor.encrypt(data) @@ -293,29 +297,36 @@ class TCPRelayHandler(object): @shell.exception_handle(self_=True, destroy=True, conn_err=True) def _handle_stage_addr(self, data): if self._is_local: - cmd = common.ord(data[1]) - if cmd == CMD_UDP_ASSOCIATE: - logging.debug('UDP associate') - if self._local_sock.family == socket.AF_INET6: - header = b'\x05\x00\x00\x04' - else: - header = b'\x05\x00\x00\x01' - addr, port = self._local_sock.getsockname()[:2] - addr_to_send = socket.inet_pton(self._local_sock.family, - addr) - port_to_send = struct.pack('>H', port) - self._write_to_sock(header + addr_to_send + port_to_send, - self._local_sock) - self._stage = STAGE_UDP_ASSOC - # just wait for the client to disconnect - return - elif cmd == CMD_CONNECT: - # just trim VER CMD RSV - data = data[3:] + if self._is_tunnel: + # add ss header to data + tunnel_remote = self.tunnel_remote + tunnel_remote_port = self.tunnel_remote_port + data = common.add_header(tunnel_remote, + tunnel_remote_port, data) else: - logging.error('unknown command %d', cmd) - self.destroy() - return + cmd = common.ord(data[1]) + if cmd == CMD_UDP_ASSOCIATE: + logging.debug('UDP associate') + if self._local_sock.family == socket.AF_INET6: + header = b'\x05\x00\x00\x04' + else: + header = b'\x05\x00\x00\x01' + addr, port = self._local_sock.getsockname()[:2] + addr_to_send = socket.inet_pton(self._local_sock.family, + addr) + port_to_send = struct.pack('>H', port) + self._write_to_sock(header + addr_to_send + port_to_send, + self._local_sock) + self._stage = STAGE_UDP_ASSOC + # just wait for the client to disconnect + return + elif cmd == CMD_CONNECT: + # just trim VER CMD RSV + data = data[3:] + else: + logging.error('unknown command %d', cmd) + self.destroy() + return header_result = parse_header(data) if header_result is None: raise Exception('can not parse header') @@ -347,10 +358,12 @@ class TCPRelayHandler(object): self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) self._stage = STAGE_DNS if self._is_local: - # forward address to remote - self._write_to_sock((b'\x05\x00\x00\x01' - b'\x00\x00\x00\x00\x10\x10'), - self._local_sock) + # jump over socks5 response + if not self._is_tunnel: + # forward address to remote + self._write_to_sock((b'\x05\x00\x00\x01' + b'\x00\x00\x00\x00\x10\x10'), + self._local_sock) # spec https://shadowsocks.org/en/spec/one-time-auth.html # ATYP & 0x10 == 0x10, then OTA is enabled. if self._ota_enable_session: @@ -558,7 +571,12 @@ class TCPRelayHandler(object): self._handle_stage_stream(data) return elif is_local and self._stage == STAGE_INIT: - self._handle_stage_init(data) + # jump over socks5 init + if self._is_tunnel: + self._handle_stage_addr(data) + return + else: + self._handle_stage_init(data) elif self._stage == STAGE_CONNECTING: self._handle_stage_connecting(data) elif (is_local and self._stage == STAGE_ADDR) or \ @@ -689,6 +707,7 @@ class TCPRelayHandler(object): class TCPRelay(object): + def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config self._is_local = is_local @@ -696,6 +715,7 @@ class TCPRelay(object): self._closed = False self._eventloop = None self._fd_to_handlers = {} + self._is_tunnel = False self._timeout = config['timeout'] self._timeouts = [] # a list for all the handlers diff --git a/shadowsocks/tunnel.py b/shadowsocks/tunnel.py new file mode 100644 index 0000000..dbfb438 --- /dev/null +++ b/shadowsocks/tunnel.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2012-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 sys +import os +import logging +import signal + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) +from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns + + +@shell.exception_handle(self_=False, exit_code=1) +def main(): + shell.check_python() + + # fix py2exe + if hasattr(sys, "frozen") and sys.frozen in \ + ("windows_exe", "console_exe"): + p = os.path.dirname(os.path.abspath(sys.executable)) + os.chdir(p) + + config = shell.get_config(True) + daemon.daemon_exec(config) + dns_resolver = asyncdns.DNSResolver() + loop = eventloop.EventLoop() + dns_resolver.add_to_loop(loop) + _config = config.copy() + _config["local_port"] = _config["tunnel_port"] + logging.info("starting tcp tunnel at %s:%d forward to %s:%d" % + (_config['local_address'], _config['local_port'], + _config['tunnel_remote'], _config['tunnel_remote_port'])) + tunnel_tcp_server = tcprelay.TCPRelay(_config, dns_resolver, True) + tunnel_tcp_server._is_tunnel = True + tunnel_tcp_server.add_to_loop(loop) + logging.info("starting udp tunnel at %s:%d forward to %s:%d" % + (_config['local_address'], _config['local_port'], + _config['tunnel_remote'], _config['tunnel_remote_port'])) + tunnel_udp_server = udprelay.UDPRelay(_config, dns_resolver, True) + tunnel_udp_server._is_tunnel = True + tunnel_udp_server.add_to_loop(loop) + + def handler(signum, _): + logging.warn('received SIGQUIT, doing graceful shutting down..') + tunnel_tcp_server.close(next_tick=True) + tunnel_udp_server.close(next_tick=True) + signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) + + def int_handler(signum, _): + sys.exit(1) + signal.signal(signal.SIGINT, int_handler) + + daemon.set_user(config.get('user', None)) + loop.run() + +if __name__ == '__main__': + main() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index ba8299b..68e7c55 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -82,6 +82,7 @@ def client_key(source_addr, server_af): class UDPRelay(object): + def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config if is_local: @@ -94,6 +95,10 @@ class UDPRelay(object): self._listen_port = config['server_port'] self._remote_addr = None self._remote_port = None + self.tunnel_remote = config.get('tunnel_remote', "8.8.8.8") + self.tunnel_remote_port = config.get('tunnel_remote_port', 53) + self.tunnel_port = config.get('tunnel_port', 53) + self._is_tunnel = False self._dns_resolver = dns_resolver self._password = common.to_bytes(config['password']) self._method = config['method'] @@ -151,12 +156,19 @@ class UDPRelay(object): if self._stat_callback: self._stat_callback(self._listen_port, len(data)) if self._is_local: - frag = common.ord(data[2]) - if frag != 0: - logging.warn('UDP drop a message since frag is not 0') - return + if self._is_tunnel: + # add ss header to data + tunnel_remote = self.tunnel_remote + tunnel_remote_port = self.tunnel_remote_port + data = common.add_header(tunnel_remote, + tunnel_remote_port, data) else: - data = data[3:] + frag = common.ord(data[2]) + if frag != 0: + logging.warn('UDP drop a message since frag is not 0') + return + else: + data = data[3:] else: data, key, iv = encrypt.decrypt_all(self._password, self._method, @@ -171,7 +183,8 @@ class UDPRelay(object): if header_result is None: return addrtype, dest_addr, dest_port, header_length = header_result - + logging.info("udp data to %s:%d from %s:%d" + % (dest_addr, dest_port, r_addr[0], r_addr[1])) if self._is_local: server_addr, server_port = self._get_a_server() else: @@ -267,9 +280,15 @@ class UDPRelay(object): if header_result is None: return addrtype, dest_addr, dest_port, header_length = header_result - response = b'\x00\x00\x00' + data + if self._is_tunnel: + # remove ss header + response = data[header_length:] + else: + response = b'\x00\x00\x00' + data client_addr = self._client_fd_to_server_addr.get(sock.fileno()) if client_addr: + logging.debug("send udp response to %s:%d" + % (client_addr[0], client_addr[1])) self._server_socket.sendto(response, client_addr) else: # this packet is from somewhere else we know From c4731de5328fd929e3f0abd58a5c02535f1f0ee5 Mon Sep 17 00:00:00 2001 From: Elvis Wang Date: Thu, 2 Mar 2017 21:28:16 +0800 Subject: [PATCH 176/196] Update README.md (#575) The shadowsocks in pypi is out of date, may install from github@master --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c7fa39..346e53e 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,12 @@ Server Debian / Ubuntu: apt-get install python-pip - pip install shadowsocks + pip install git+https://github.com/shadowsocks/shadowsocks.git@master CentOS: yum install python-setuptools && easy_install pip - pip install shadowsocks + pip install git+https://github.com/shadowsocks/shadowsocks.git@master Windows: From 445a3c9c7e34116c47718ebbcb6e07820e8963f0 Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Sat, 4 Mar 2017 14:37:29 +0800 Subject: [PATCH 177/196] Add AEAD ciphers support (#775) * Add AEAD ciphers support, add manger api * fix test_encrypt_all * Add manager api requirements * #775 fix UDP decrypt_all issue * fix udp replay: decrypt_all return a list remove manager api * fix indent according pep8 * remove abc requirement * remove unused import * fix pep8 format * fix test_aes_256_gcm() --- .gitignore | 8 +- setup.py | 2 +- shadowsocks/crypto/aead.py | 285 ++++++++++++++++++++++++++++++++++ shadowsocks/crypto/hkdf.py | 98 ++++++++++++ shadowsocks/crypto/openssl.py | 258 +++++++++++++++++++++++++----- shadowsocks/crypto/rc4_md5.py | 3 +- shadowsocks/crypto/sodium.py | 165 ++++++++++++++++++++ shadowsocks/crypto/table.py | 4 + shadowsocks/crypto/util.py | 27 +++- shadowsocks/cryptor.py | 235 ++++++++++++++++++++++++++++ shadowsocks/encrypt.py | 4 +- shadowsocks/manager.py | 6 +- shadowsocks/shell.py | 28 +++- shadowsocks/tcprelay.py | 28 ++-- shadowsocks/udprelay.py | 15 +- 15 files changed, 1086 insertions(+), 80 deletions(-) create mode 100644 shadowsocks/crypto/aead.py create mode 100644 shadowsocks/crypto/hkdf.py create mode 100644 shadowsocks/cryptor.py diff --git a/.gitignore b/.gitignore index 0b36d7d..e6f83bd 100644 --- a/.gitignore +++ b/.gitignore @@ -29,13 +29,17 @@ htmlcov .DS_Store .idea +tags #Emacs .#* +venv/ -#vscode +# VS-code +.vscode/ + +# Pycharm .idea -.vscode #ss config.json diff --git a/setup.py b/setup.py index b27c8eb..f41f481 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.9.1", + version="3.0.0", 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/crypto/aead.py b/shadowsocks/crypto/aead.py new file mode 100644 index 0000000..1ca06f2 --- /dev/null +++ b/shadowsocks/crypto/aead.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Void Copyright NO ONE +# +# Void License +# +# The code belongs to no one. Do whatever you want. +# Forget about boring open source license. +# +# AEAD cipher for shadowsocks +# + +from __future__ import absolute_import, division, print_function, \ + with_statement + +from ctypes import create_string_buffer + +import hashlib +from struct import pack, unpack + +from shadowsocks.crypto import hkdf +from shadowsocks.common import ord, chr + + +EVP_CTRL_GCM_SET_IVLEN = 0x9 +EVP_CTRL_GCM_GET_TAG = 0x10 +EVP_CTRL_GCM_SET_TAG = 0x11 +EVP_CTRL_CCM_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN +EVP_CTRL_CCM_GET_TAG = EVP_CTRL_GCM_GET_TAG +EVP_CTRL_CCM_SET_TAG = EVP_CTRL_GCM_SET_TAG + +EVP_CTRL_AEAD_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN +EVP_CTRL_AEAD_SET_TAG = EVP_CTRL_GCM_SET_TAG +EVP_CTRL_AEAD_GET_TAG = EVP_CTRL_GCM_GET_TAG + +AEAD_MSG_LEN_UNKNOWN = 0 +AEAD_CHUNK_SIZE_LEN = 2 +AEAD_CHUNK_SIZE_MASK = 0x3FFF + +CIPHER_NONCE_LEN = { + 'aes-128-gcm': 12, + 'aes-192-gcm': 12, + 'aes-256-gcm': 12, + 'chacha20-poly1305': 12, + 'chacha20-ietf-poly1305': 12, + 'xchacha20-ietf-poly1305': 24, +} + +CIPHER_TAG_LEN = { + 'aes-128-gcm': 16, + 'aes-192-gcm': 16, + 'aes-256-gcm': 16, + 'chacha20-poly1305': 16, + 'chacha20-ietf-poly1305': 16, + 'xchacha20-ietf-poly1305': 16, +} + +SUBKEY_INFO = b"ss-subkey" + + +def nonce_increment(nonce, nlen): + """ + Increase nonce by 1 in little endian + From libsodium sodium_increment(): + for (; i < nlen; i++) { + c += (uint_fast16_t) n[i]; + n[i] = (unsigned char) c; + c >>= 8; + } + :param nonce: string_buffer nonce + :param nlen: nonce length + :return: nonce plus by 1 + """ + c = 1 + i = 0 + # n = create_string_buffer(nlen) + while i < nlen: + c += ord(nonce[i]) + nonce[i] = chr(c & 0xFF) + c >>= 8 + i += 1 + return # n.raw + + +class AeadCryptoBase(object): + """ + Handles basic aead process of shadowsocks protocol + + TCP Chunk (after encryption, *ciphertext*) + +--------------+---------------+--------------+------------+ + | *DataLen* | DataLen_TAG | *Data* | Data_TAG | + +--------------+---------------+--------------+------------+ + | 2 | Fixed | Variable | Fixed | + +--------------+---------------+--------------+------------+ + + UDP (after encryption, *ciphertext*) + +--------+-----------+-----------+ + | NONCE | *Data* | Data_TAG | + +-------+-----------+-----------+ + | Fixed | Variable | Fixed | + +--------+-----------+-----------+ + """ + + def __init__(self, cipher_name, key, iv, op): + self._op = int(op) + self._salt = iv + self._nlen = CIPHER_NONCE_LEN[cipher_name] + self._nonce = create_string_buffer(self._nlen) + self._tlen = CIPHER_TAG_LEN[cipher_name] + + crypto_hkdf = hkdf.Hkdf(iv, key, algorithm=hashlib.sha1) + self._skey = crypto_hkdf.expand(info=SUBKEY_INFO, length=len(key)) + # _chunk['mlen']: + # -1, waiting data len header + # n, n > 0, waiting data + self._chunk = {'mlen': AEAD_MSG_LEN_UNKNOWN, 'data': b''} + + self.encrypt_once = self.aead_encrypt + self.decrypt_once = self.aead_decrypt + + def cipher_ctx_init(self): + """ + Increase nonce to make it unique for the same key + :return: void + """ + nonce_increment(self._nonce, self._nlen) + # print("".join("%02x" % ord(b) for b in self._nonce)) + + def aead_encrypt(self, data): + """ + Encrypt data with authenticate tag + + :param data: plain text + :return: cipher text with tag + """ + raise Exception("Must implement aead_encrypt method") + + def encrypt_chunk(self, data): + """ + Encrypt a chunk for TCP chunks + + :param data: str + :return: (str, int) + """ + plen = len(data) + l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2 + + # network byte order + ctext = self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK)) + if len(ctext) != AEAD_CHUNK_SIZE_LEN + self._tlen: + raise Exception("data length invalid") + + self.cipher_ctx_init() + ctext += self.aead_encrypt(data) + if len(ctext) != l: + raise Exception("data length invalid") + + self.cipher_ctx_init() + return ctext, l + + def encrypt(self, data): + """ + Encrypt data, for TCP divided into chunks + For UDP data, call aead_encrypt instead + + :param data: str data bytes + :return: str encrypted data + """ + plen = len(data) + if plen <= AEAD_CHUNK_SIZE_MASK: + ctext, _ = self.encrypt_chunk(data) + return ctext + ctext, clen = b"", 0 + while plen > 0: + mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \ + else AEAD_CHUNK_SIZE_MASK + r, l = self.encrypt_chunk(data[:mlen]) + ctext += r + clen += l + data = data[mlen:] + plen -= mlen + + return ctext + + def aead_decrypt(self, data): + """ + Decrypt data and authenticate tag + + :param data: str cipher text with tag + :return: str plain text + """ + raise Exception("Must implement aead_decrypt method") + + def decrypt_chunk_size(self, data): + """ + Decrypt chunk size + + :param data: str encrypted msg + :return: (int, str) msg length and remaining encrypted data + """ + if self._chunk['mlen'] > 0: + return self._chunk['mlen'], data + data = self._chunk['data'] + data + self._chunk['data'] = b"" + + hlen = AEAD_CHUNK_SIZE_LEN + self._tlen + if hlen > len(data): + self._chunk['data'] = data + return 0, b"" + plen = self.aead_decrypt(data[:hlen]) + plen, = unpack("!H", plen) + if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0: + raise Exception('Invalid message length') + + self.cipher_ctx_init() + return plen, data[hlen:] + + def decrypt_chunk_payload(self, plen, data): + """ + Decrypted encrypted msg payload + + :param plen: int payload length + :param data: str encrypted data + :return: (str, str) plain text and remaining encrypted data + """ + data = self._chunk['data'] + data + if len(data) < plen + self._tlen: + self._chunk['mlen'] = plen + self._chunk['data'] = data + return b"", b"" + self._chunk['mlen'] = AEAD_MSG_LEN_UNKNOWN + self._chunk['data'] = b"" + + plaintext = self.aead_decrypt(data[:plen + self._tlen]) + + if len(plaintext) != plen: + raise Exception("plaintext length invalid") + + self.cipher_ctx_init() + + return plaintext, data[plen + self._tlen:] + + def decrypt_chunk(self, data): + """ + Decrypt a TCP chunk + + :param data: str encrypted msg + :return: (str, str) decrypted msg and remaining encrypted data + """ + plen, data = self.decrypt_chunk_size(data) + if plen <= 0: + return b"", b"" + return self.decrypt_chunk_payload(plen, data) + + def decrypt(self, data): + """ + Decrypt data for TCP data divided into chunks + For UDP data, call aead_decrypt instead + + :param data: str + :return: str + """ + ptext, left = self.decrypt_chunk(data) + while len(left) > 0: + pnext, left = self.decrypt_chunk(left) + ptext += pnext + return ptext + + +def test_nonce_increment(): + buf = create_string_buffer(12) + print("".join("%02x" % ord(b) for b in buf)) + nonce_increment(buf, 12) + nonce_increment(buf, 12) + nonce_increment(buf, 12) + nonce_increment(buf, 12) + print("".join("%02x" % ord(b) for b in buf)) + for i in range(256): + nonce_increment(buf, 12) + print("".join("%02x" % ord(b) for b in buf)) + + +if __name__ == '__main__': + test_nonce_increment() diff --git a/shadowsocks/crypto/hkdf.py b/shadowsocks/crypto/hkdf.py new file mode 100644 index 0000000..11998e6 --- /dev/null +++ b/shadowsocks/crypto/hkdf.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Void Copyright NO ONE +# +# Void License +# +# The code belongs to no one. Do whatever you want. +# Forget about boring open source license. +# +# HKDF for AEAD ciphers +# + +from __future__ import division + +import hmac +import hashlib +import sys + +if sys.version_info[0] == 3: + def buffer(x): + return x + + +def hkdf_extract(salt, input_key_material, algorithm=hashlib.sha256): + """ + Extract a pseudorandom key suitable for use with hkdf_expand + from the input_key_material and a salt using HMAC with the + provided hash (default SHA-256). + + salt should be a random, application-specific byte string. If + salt is None or the empty string, an all-zeros string of the same + length as the hash's block size will be used instead per the RFC. + + See the HKDF draft RFC and paper for usage notes. + """ + hash_len = algorithm().digest_size + if salt is None or len(salt) == 0: + salt = bytearray((0,) * hash_len) + return hmac.new(bytes(salt), buffer(input_key_material), algorithm)\ + .digest() + + +def hkdf_expand(pseudo_random_key, info=b"", length=32, + algorithm=hashlib.sha256): + """ + Expand `pseudo_random_key` and `info` into a key of length `bytes` using + HKDF's expand function based on HMAC with the provided hash (default + SHA-256). See the HKDF draft RFC and paper for usage notes. + """ + hash_len = algorithm().digest_size + length = int(length) + if length > 255 * hash_len: + raise Exception("Cannot expand to more than 255 * %d = %d " + "bytes using the specified hash function" % + (hash_len, 255 * hash_len)) + blocks_needed = length // hash_len \ + + (0 if length % hash_len == 0 else 1) # ceil + okm = b"" + output_block = b"" + for counter in range(blocks_needed): + output_block = hmac.new( + pseudo_random_key, + buffer(output_block + info + bytearray((counter + 1,))), + algorithm + ).digest() + okm += output_block + return okm[:length] + + +class Hkdf(object): + """ + Wrapper class for HKDF extract and expand functions + """ + + def __init__(self, salt, input_key_material, algorithm=hashlib.sha256): + """ + Extract a pseudorandom key from `salt` and `input_key_material` + arguments. + + See the HKDF draft RFC for guidance on setting these values. + The constructor optionally takes a `algorithm` argument defining + the hash function use, defaulting to hashlib.sha256. + """ + self._hash = algorithm + self._prk = hkdf_extract(salt, input_key_material, self._hash) + + def expand(self, info, length=32): + """ + Generate output key material based on an `info` value + + Arguments: + - info - context to generate the OKM + - length - length in bytes of the key to generate + + See the HKDF draft RFC for guidance. + """ + return hkdf_expand(self._prk, info, length, self._hash) diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index da7f177..50cdc22 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -22,6 +22,8 @@ from ctypes import c_char_p, c_int, c_long, byref,\ from shadowsocks import common from shadowsocks.crypto import util +from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \ + nonce_increment, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG __all__ = ['ciphers'] @@ -30,6 +32,8 @@ loaded = False buf_size = 2048 +CIPHER_ENC_UNCHANGED = -1 + def load_openssl(): global loaded, libcrypto, buf, ctx_cleanup @@ -45,10 +49,13 @@ def load_openssl(): libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, c_char_p, c_char_p, c_int) + libcrypto.EVP_CIPHER_CTX_ctrl.argtypes = (c_void_p, c_int, c_int, c_void_p) libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, c_char_p, c_int) + libcrypto.EVP_CipherFinal_ex.argtypes = (c_void_p, c_void_p, c_void_p) + try: libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup @@ -64,7 +71,7 @@ def load_openssl(): def load_cipher(cipher_name): - func_name = 'EVP_' + cipher_name.replace('-', '_') + func_name = b'EVP_' + cipher_name.replace(b'-', b'_') if bytes != str: func_name = str(func_name, 'utf-8') cipher = getattr(libcrypto, func_name, None) @@ -74,9 +81,13 @@ def load_cipher(cipher_name): return None -class OpenSSLCrypto(object): - def __init__(self, cipher_name, key, iv, op): +class OpenSSLCryptoBase(object): + """ + OpenSSL crypto base class + """ + def __init__(self, cipher_name): self._ctx = None + self._cipher = None if not loaded: load_openssl() cipher_name = common.to_bytes(cipher_name) @@ -85,26 +96,30 @@ class OpenSSLCrypto(object): cipher = load_cipher(cipher_name) if not cipher: raise Exception('cipher %s not found in libcrypto' % cipher_name) - key_ptr = c_char_p(key) - iv_ptr = c_char_p(iv) self._ctx = libcrypto.EVP_CIPHER_CTX_new() + self._cipher = cipher if not self._ctx: raise Exception('can not create cipher context') - r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, - key_ptr, iv_ptr, c_int(op)) - if not r: - self.clean() - raise Exception('can not initialize cipher context') + + self.encrypt_once = self.update + self.decrypt_once = self.update def update(self, data): + """ + Encrypt/decrypt data + :param data: str + :return: str + """ global buf_size, buf cipher_out_len = c_long(0) l = len(data) if buf_size < l: buf_size = l * 2 buf = create_string_buffer(buf_size) - libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), - byref(cipher_out_len), c_char_p(data), l) + libcrypto.EVP_CipherUpdate( + self._ctx, byref(buf), + byref(cipher_out_len), c_char_p(data), l + ) # buf is copied to a str object when we access buf.raw return buf.raw[:cipher_out_len.value] @@ -117,39 +132,190 @@ class OpenSSLCrypto(object): libcrypto.EVP_CIPHER_CTX_free(self._ctx) +class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): + """ + Implement OpenSSL Aead mode: gcm, ocb + """ + def __init__(self, cipher_name, key, iv, op): + super(OpenSSLAeadCrypto, self).__init__(cipher_name) + AeadCryptoBase.__init__(self, cipher_name, key, iv, op) + + r = libcrypto.EVP_CipherInit_ex( + self._ctx, + self._cipher, None, + None, None, c_int(op) + ) + if not r: + self.clean() + raise Exception('can not initialize cipher context') + + r = libcrypto.EVP_CIPHER_CTX_ctrl( + self._ctx, + c_int(EVP_CTRL_AEAD_SET_IVLEN), + c_int(self._nlen), + None + ) + if not r: + raise Exception('Set ivlen failed') + + self.cipher_ctx_init() + + def cipher_ctx_init(self): + """ + Need init cipher context after EVP_CipherFinal_ex to reuse context + :return: void + """ + key_ptr = c_char_p(self._skey) + iv_ptr = c_char_p(self._nonce.raw) + + r = libcrypto.EVP_CipherInit_ex( + self._ctx, + None, None, + key_ptr, iv_ptr, + c_int(CIPHER_ENC_UNCHANGED) + ) + if not r: + self.clean() + raise Exception('can not initialize cipher context') + + nonce_increment(self._nonce, self._nlen) + # print("".join("%02x" % ord(b) for b in self._nonce)) + + def set_tag(self, tag): + """ + Set tag before decrypt any data (update) + :param tag: authenticated tag + :return: void + """ + tag_len = self._tlen + r = libcrypto.EVP_CIPHER_CTX_ctrl( + self._ctx, + c_int(EVP_CTRL_AEAD_SET_TAG), + c_int(tag_len), c_char_p(tag) + ) + if not r: + raise Exception('Set tag failed') + + def get_tag(self): + """ + Get authenticated tag, called after EVP_CipherFinal_ex + :return: str + """ + tag_len = self._tlen + tag_buf = create_string_buffer(tag_len) + r = libcrypto.EVP_CIPHER_CTX_ctrl( + self._ctx, + c_int(EVP_CTRL_AEAD_GET_TAG), + c_int(tag_len), byref(tag_buf) + ) + if not r: + raise Exception('Get tag failed') + return tag_buf.raw[:tag_len] + + def final(self): + """ + Finish encrypt/decrypt a chunk (<= 0x3FFF) + :return: str + """ + global buf_size, buf + cipher_out_len = c_long(0) + r = libcrypto.EVP_CipherFinal_ex( + self._ctx, + byref(buf), byref(cipher_out_len) + ) + if not r: + # print(self._nonce.raw, r, cipher_out_len) + raise Exception('Verify data failed') + return buf.raw[:cipher_out_len.value] + + def aead_encrypt(self, data): + """ + Encrypt data with authenticate tag + + :param data: plain text + :return: cipher text with tag + """ + ctext = self.update(data) + self.final() + self.get_tag() + return ctext + + def aead_decrypt(self, data): + """ + Decrypt data and authenticate tag + + :param data: cipher text with tag + :return: plain text + """ + clen = len(data) + if clen < self._tlen: + raise Exception('Data too short') + + self.set_tag(data[clen - self._tlen:]) + plaintext = self.update(data[:clen - self._tlen]) + self.final() + return plaintext + + +class OpenSSLStreamCrypto(OpenSSLCryptoBase): + """ + Crypto for stream modes: cfb, ofb, ctr + """ + def __init__(self, cipher_name, key, iv, op): + super(OpenSSLStreamCrypto, self).__init__(cipher_name) + key_ptr = c_char_p(key) + iv_ptr = c_char_p(iv) + r = libcrypto.EVP_CipherInit_ex(self._ctx, self._cipher, None, + key_ptr, iv_ptr, c_int(op)) + if not r: + self.clean() + raise Exception('can not initialize cipher context') + self.encrypt = self.update + self.decrypt = self.update + + ciphers = { - '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), + 'aes-128-cfb': (16, 16, OpenSSLStreamCrypto), + 'aes-192-cfb': (24, 16, OpenSSLStreamCrypto), + 'aes-256-cfb': (32, 16, OpenSSLStreamCrypto), + 'aes-128-gcm': (16, 16, OpenSSLAeadCrypto), + 'aes-192-gcm': (24, 24, OpenSSLAeadCrypto), + 'aes-256-gcm': (32, 32, OpenSSLAeadCrypto), + 'aes-128-ofb': (16, 16, OpenSSLStreamCrypto), + 'aes-192-ofb': (24, 16, OpenSSLStreamCrypto), + 'aes-256-ofb': (32, 16, OpenSSLStreamCrypto), + 'aes-128-ctr': (16, 16, OpenSSLStreamCrypto), + 'aes-192-ctr': (24, 16, OpenSSLStreamCrypto), + 'aes-256-ctr': (32, 16, OpenSSLStreamCrypto), + 'aes-128-cfb8': (16, 16, OpenSSLStreamCrypto), + 'aes-192-cfb8': (24, 16, OpenSSLStreamCrypto), + 'aes-256-cfb8': (32, 16, OpenSSLStreamCrypto), + 'aes-128-cfb1': (16, 16, OpenSSLStreamCrypto), + 'aes-192-cfb1': (24, 16, OpenSSLStreamCrypto), + 'aes-256-cfb1': (32, 16, OpenSSLStreamCrypto), + 'bf-cfb': (16, 8, OpenSSLStreamCrypto), + 'camellia-128-cfb': (16, 16, OpenSSLStreamCrypto), + 'camellia-192-cfb': (24, 16, OpenSSLStreamCrypto), + 'camellia-256-cfb': (32, 16, OpenSSLStreamCrypto), + 'cast5-cfb': (16, 8, OpenSSLStreamCrypto), + 'des-cfb': (8, 8, OpenSSLStreamCrypto), + 'idea-cfb': (16, 8, OpenSSLStreamCrypto), + 'rc2-cfb': (16, 8, OpenSSLStreamCrypto), + 'rc4': (16, 0, OpenSSLStreamCrypto), + 'seed-cfb': (16, 16, OpenSSLStreamCrypto), } def run_method(method): - cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1) - decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0) + cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1) + decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + +def run_aead_method(method, key_len=16): + + key_len = int(key_len) + cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) + decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0) util.run_cipher(cipher, decipher) @@ -158,6 +324,16 @@ def test_aes_128_cfb(): run_method('aes-128-cfb') +def test_aes_gcm(bits=128): + method = "aes-{0}-gcm".format(bits) + print(method, int(bits / 8)) + run_aead_method(method, bits / 8) + + +def test_aes_256_gcm(): + test_aes_gcm(256) + + def test_aes_256_cfb(): run_method('aes-256-cfb') @@ -184,3 +360,7 @@ def test_rc4(): if __name__ == '__main__': test_aes_128_cfb() + test_aes_gcm(128) + test_aes_gcm(192) + test_aes_gcm(256) + test_aes_256_gcm() diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 1f07a82..2f7f22e 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -18,7 +18,6 @@ from __future__ import absolute_import, division, print_function, \ with_statement import hashlib - from shadowsocks.crypto import openssl __all__ = ['ciphers'] @@ -30,7 +29,7 @@ def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, md5.update(key) md5.update(iv) rc4_key = md5.digest() - return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op) + return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op) ciphers = { diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index b744e2c..48a459f 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -21,6 +21,7 @@ from ctypes import c_char_p, c_int, c_ulonglong, byref, c_ulong, \ create_string_buffer, c_void_p from shadowsocks.crypto import util +from shadowsocks.crypto.aead import AeadCryptoBase __all__ = ['ciphers'] @@ -59,6 +60,67 @@ def load_libsodium(): c_ulong, c_char_p) + # chacha20-poly1305 + libsodium.crypto_aead_chacha20poly1305_encrypt.restype = c_int + libsodium.crypto_aead_chacha20poly1305_encrypt.argtypes = ( + c_void_p, c_void_p, # c, clen + c_char_p, c_ulonglong, # m, mlen + c_char_p, c_ulonglong, # ad, adlen + c_char_p, # nsec, not used + c_char_p, c_char_p # npub, k + ) + libsodium.crypto_aead_chacha20poly1305_decrypt.restype = c_int + libsodium.crypto_aead_chacha20poly1305_decrypt.argtypes = ( + c_void_p, c_void_p, # m, mlen + c_char_p, # nsec, not used + c_char_p, c_ulonglong, # c, clen + c_char_p, c_ulonglong, # ad, adlen + c_char_p, c_char_p # npub, k + ) + + # chacha20-ietf-poly1305, same api structure as above + libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.restype = c_int + libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.argtypes = ( + c_void_p, c_void_p, + c_char_p, c_ulonglong, + c_char_p, c_ulonglong, + c_char_p, + c_char_p, c_char_p + ) + libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.restype = c_int + libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.argtypes = ( + c_void_p, c_void_p, + c_char_p, + c_char_p, c_ulonglong, + c_char_p, c_ulonglong, + c_char_p, c_char_p + ) + + # xchacha20-ietf-poly1305, same api structure as above + if hasattr(libsodium, 'crypto_aead_xchacha20poly1305_ietf_encrypt'): + libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.restype = c_int + libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.argtypes = ( + c_void_p, c_void_p, + c_char_p, c_ulonglong, + c_char_p, c_ulonglong, + c_char_p, + c_char_p, c_char_p + ) + + libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.restype = c_int + libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.argtypes = ( + c_void_p, c_void_p, + c_char_p, + c_char_p, c_ulonglong, + c_char_p, c_ulonglong, + c_char_p, c_char_p + ) + + libsodium.sodium_increment.restype = c_void_p + libsodium.sodium_increment.argtypes = ( + c_void_p, c_int + ) + buf = create_string_buffer(buf_size) loaded = True @@ -81,6 +143,10 @@ class SodiumCrypto(object): raise Exception('Unknown cipher') # byte counter, not block counter self.counter = 0 + self.encrypt = self.update + self.decrypt = self.update + self.encrypt_once = self.update + self.decrypt_once = self.update def update(self, data): global buf_size, buf @@ -103,10 +169,85 @@ class SodiumCrypto(object): return buf.raw[padding:padding + l] +class SodiumAeadCrypto(AeadCryptoBase): + def __init__(self, cipher_name, key, iv, op): + if not loaded: + load_libsodium() + AeadCryptoBase.__init__(self, cipher_name, key, iv, op) + + if cipher_name == 'chacha20-poly1305': + self.encryptor = libsodium.crypto_aead_chacha20poly1305_encrypt + self.decryptor = libsodium.crypto_aead_chacha20poly1305_decrypt + elif cipher_name == 'chacha20-ietf-poly1305': + self.encryptor = libsodium.\ + crypto_aead_chacha20poly1305_ietf_encrypt + self.decryptor = libsodium.\ + crypto_aead_chacha20poly1305_ietf_decrypt + elif cipher_name == 'xchacha20-ietf-poly1305': + if hasattr(libsodium, + 'crypto_aead_xchacha20poly1305_ietf_encrypt'): + self.encryptor = libsodium.\ + crypto_aead_xchacha20poly1305_ietf_encrypt + self.decryptor = libsodium.\ + crypto_aead_xchacha20poly1305_ietf_decrypt + else: + raise Exception('Unknown cipher') + else: + raise Exception('Unknown cipher') + + def cipher_ctx_init(self): + + libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) + # print("".join("%02x" % ord(b) for b in self._nonce)) + + def aead_encrypt(self, data): + global buf, buf_size + plen = len(data) + if buf_size < plen + self._tlen: + buf_size = (plen + self._tlen) * 2 + buf = create_string_buffer(buf_size) + cipher_out_len = c_ulonglong(0) + self.encryptor( + byref(buf), byref(cipher_out_len), + c_char_p(data), c_ulonglong(plen), + None, c_ulonglong(0), None, + c_char_p(self._nonce.raw), c_char_p(self._skey) + ) + if cipher_out_len.value != plen + self._tlen: + raise Exception("Encrypt failed") + + return buf.raw[:cipher_out_len.value] + + def aead_decrypt(self, data): + global buf, buf_size + clen = len(data) + if buf_size < clen: + buf_size = clen * 2 + buf = create_string_buffer(buf_size) + cipher_out_len = c_ulonglong(0) + r = self.decryptor( + byref(buf), byref(cipher_out_len), + None, + c_char_p(data), c_ulonglong(clen), + None, c_ulonglong(0), + c_char_p(self._nonce.raw), c_char_p(self._skey) + ) + if r != 0: + raise Exception("Decrypt failed") + + if cipher_out_len.value != clen - self._tlen: + raise Exception("Encrypt failed") + + return buf.raw[:cipher_out_len.value] + + ciphers = { 'salsa20': (32, 8, SodiumCrypto), 'chacha20': (32, 8, SodiumCrypto), 'chacha20-ietf': (32, 12, SodiumCrypto), + 'chacha20-poly1305': (32, 32, SodiumAeadCrypto), + 'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), + 'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), } @@ -133,7 +274,31 @@ def test_chacha20_ietf(): util.run_cipher(cipher, decipher) +def test_chacha20_poly1305(): + + print("Test chacha20-poly1305") + cipher = SodiumAeadCrypto('chacha20-poly1305', + b'k' * 32, b'i' * 32, 1) + decipher = SodiumAeadCrypto('chacha20-poly1305', + b'k' * 32, b'i' * 32, 0) + + util.run_cipher(cipher, decipher) + + +def test_chacha20_ietf_poly1305(): + + print("Test chacha20-ietf-poly1305") + cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', + b'k' * 32, b'i' * 32, 1) + decipher = SodiumAeadCrypto('chacha20-ietf-poly1305', + b'k' * 32, b'i' * 32, 0) + + util.run_cipher(cipher, decipher) + + if __name__ == '__main__': test_chacha20() test_salsa20() test_chacha20_ietf() + test_chacha20_poly1305() + test_chacha20_ietf_poly1305() diff --git a/shadowsocks/crypto/table.py b/shadowsocks/crypto/table.py index bc693f5..9e11002 100644 --- a/shadowsocks/crypto/table.py +++ b/shadowsocks/crypto/table.py @@ -58,6 +58,10 @@ class TableCipher(object): def __init__(self, cipher_name, key, iv, op): self._encrypt_table, self._decrypt_table = init_table(key) self._op = op + self.encrypt = self.update + self.decrypt = self.update + self.encrypt_once = self.update + self.decrypt_once = self.update def update(self, data): if self._op: diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index e579455..bb23197 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -33,7 +33,7 @@ def find_library_nt(name): results.append(fname) if fname.lower().endswith(".dll"): continue - fname = fname + ".dll" + fname += ".dll" if os.path.isfile(fname): results.append(fname) return results @@ -92,14 +92,27 @@ def find_library(possible_lib_names, search_symbol, library_name): return None +def parse_mode(cipher_nme): + """ + Parse the cipher mode from cipher name + e.g. aes-128-gcm, the mode is gcm + :param cipher_nme: str cipher name, aes-128-cfb, aes-128-gcm ... + :return: str/None The mode, cfb, gcm ... + """ + hyphen = cipher_nme.rfind('-') + if hyphen > 0: + return cipher_nme[hyphen:] + return None + + def run_cipher(cipher, decipher): from os import urandom import random import time - BLOCK_SIZE = 16384 + block_size = 16384 rounds = 1 * 1024 - plain = urandom(BLOCK_SIZE * rounds) + plain = urandom(block_size * rounds) results = [] pos = 0 @@ -107,18 +120,18 @@ def run_cipher(cipher, decipher): start = time.time() while pos < len(plain): l = random.randint(100, 32768) - c = cipher.update(plain[pos:pos + l]) + c = cipher.encrypt(plain[pos:pos + l]) results.append(c) pos += l pos = 0 c = b''.join(results) results = [] - while pos < len(plain): + while pos < len(c): l = random.randint(100, 32768) - results.append(decipher.update(c[pos:pos + l])) + results.append(decipher.decrypt(c[pos:pos + l])) pos += l end = time.time() - print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start))) + print('speed: %d bytes/s' % (block_size * rounds / (end - start))) assert b''.join(results) == plain diff --git a/shadowsocks/cryptor.py b/shadowsocks/cryptor.py new file mode 100644 index 0000000..719b1b6 --- /dev/null +++ b/shadowsocks/cryptor.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +# +# Copyright 2012-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 os +import sys +import hashlib +import logging + +from shadowsocks import common +from shadowsocks.crypto import rc4_md5, openssl, sodium, table + + +CIPHER_ENC_ENCRYPTION = 1 +CIPHER_ENC_DECRYPTION = 0 + +METHOD_INFO_KEY_LEN = 0 +METHOD_INFO_IV_LEN = 1 +METHOD_INFO_CRYPTO = 2 + +method_supported = {} +method_supported.update(rc4_md5.ciphers) +method_supported.update(openssl.ciphers) +method_supported.update(sodium.ciphers) +method_supported.update(table.ciphers) + + +def random_string(length): + return os.urandom(length) + +cached_keys = {} + + +def try_cipher(key, method=None): + Cryptor(key, method) + + +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 + cached_key = '%s-%d-%d' % (password, key_len, iv_len) + r = cached_keys.get(cached_key, None) + if r: + return r + m = [] + i = 0 + while len(b''.join(m)) < (key_len + iv_len): + md5 = hashlib.md5() + data = password + if i > 0: + data = m[i - 1] + password + md5.update(data) + m.append(md5.digest()) + i += 1 + ms = b''.join(m) + key = ms[:key_len] + iv = ms[key_len:key_len + iv_len] + cached_keys[cached_key] = (key, iv) + return key, iv + + +class Cryptor(object): + def __init__(self, password, method): + self.password = password + self.key = None + self.method = method + self.iv_sent = False + self.cipher_iv = b'' + self.decipher = None + self.decipher_iv = None + method = method.lower() + self._method_info = Cryptor.get_method_info(method) + if self._method_info: + self.cipher = self.get_cipher( + password, method, CIPHER_ENC_ENCRYPTION, + random_string(self._method_info[METHOD_INFO_IV_LEN]) + ) + else: + logging.error('method %s not supported' % method) + sys.exit(1) + + @staticmethod + def get_method_info(method): + method = method.lower() + m = method_supported.get(method) + return m + + def iv_len(self): + return len(self.cipher_iv) + + def get_cipher(self, password, method, op, iv): + password = common.to_bytes(password) + m = self._method_info + if m[METHOD_INFO_KEY_LEN] > 0: + key, _ = EVP_BytesToKey(password, + m[METHOD_INFO_KEY_LEN], + m[METHOD_INFO_IV_LEN]) + else: + # key_length == 0 indicates we should use the key directly + key, iv = password, b'' + self.key = key + iv = iv[:m[METHOD_INFO_IV_LEN]] + if op == CIPHER_ENC_ENCRYPTION: + # this iv is for cipher not decipher + self.cipher_iv = iv + return m[METHOD_INFO_CRYPTO](method, key, iv, op) + + def encrypt(self, buf): + if len(buf) == 0: + return buf + if self.iv_sent: + return self.cipher.encrypt(buf) + else: + self.iv_sent = True + return self.cipher_iv + self.cipher.encrypt(buf) + + def decrypt(self, buf): + if len(buf) == 0: + return buf + if self.decipher is None: + decipher_iv_len = self._method_info[METHOD_INFO_IV_LEN] + decipher_iv = buf[:decipher_iv_len] + self.decipher_iv = decipher_iv + self.decipher = self.get_cipher( + self.password, self.method, + CIPHER_ENC_DECRYPTION, + iv=decipher_iv + ) + buf = buf[decipher_iv_len:] + if len(buf) == 0: + return buf + return self.decipher.decrypt(buf) + + +def gen_key_iv(password, method): + method = method.lower() + (key_len, iv_len, m) = method_supported[method] + if key_len > 0: + key, _ = EVP_BytesToKey(password, key_len, iv_len) + else: + key = password + iv = random_string(iv_len) + return key, iv, m + + +def encrypt_all_m(key, iv, m, method, data): + result = [iv] + cipher = m(method, key, iv, 1) + result.append(cipher.encrypt_once(data)) + return b''.join(result) + + +def decrypt_all(password, method, data): + result = [] + method = method.lower() + (key, iv, m) = gen_key_iv(password, method) + iv = data[:len(iv)] + data = data[len(iv):] + cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION) + result.append(cipher.decrypt_once(data)) + return b''.join(result), key, iv + + +def encrypt_all(password, method, data): + result = [] + method = method.lower() + (key, iv, m) = gen_key_iv(password, method) + result.append(iv) + cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION) + result.append(cipher.encrypt_once(data)) + return b''.join(result) + + +CIPHERS_TO_TEST = [ + 'aes-128-cfb', + 'aes-256-cfb', + 'rc4-md5', + 'salsa20', + 'chacha20', + 'table', +] + + +def test_encryptor(): + from os import urandom + plain = urandom(10240) + for method in CIPHERS_TO_TEST: + logging.warn(method) + encryptor = Cryptor(b'key', method) + decryptor = Cryptor(b'key', method) + cipher = encryptor.encrypt(plain) + plain2 = decryptor.decrypt(cipher) + assert plain == plain2 + + +def test_encrypt_all(): + from os import urandom + plain = urandom(10240) + for method in CIPHERS_TO_TEST: + logging.warn(method) + cipher = encrypt_all(b'key', method, plain) + plain2, key, iv = decrypt_all(b'key', method, cipher) + assert plain == plain2 + + +def test_encrypt_all_m(): + from os import urandom + plain = urandom(10240) + for method in CIPHERS_TO_TEST: + logging.warn(method) + key, iv, m = gen_key_iv(b'key', method) + cipher = encrypt_all_m(key, iv, m, method, plain) + plain2, key, iv = decrypt_all(b'key', method, cipher) + assert plain == plain2 + + +if __name__ == '__main__': + test_encrypt_all() + test_encryptor() + test_encrypt_all_m() diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index a86058b..ece72ec 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -153,7 +153,7 @@ def encrypt_all_m(key, iv, m, method, data): return b''.join(result) -def decrypt_all(password, method, data): +def dencrypt_all(password, method, data): result = [] method = method.lower() (key_len, iv_len, m) = method_supported[method] @@ -228,7 +228,7 @@ def test_encrypt_all_m(): logging.warn(method) key, iv, m = gen_key_iv(b'key', method) cipher = encrypt_all_m(key, iv, m, method, plain) - plain2, key, iv = decrypt_all(b'key', method, cipher) + plain2, key, iv = dencrypt_all(b'key', method, cipher) assert plain == plain2 diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index f3a7b77..55aea86 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -202,7 +202,7 @@ def test(): import time import threading import struct - from shadowsocks import encrypt + from shadowsocks import cryptor logging.basicConfig(level=5, format='%(asctime)s %(levelname)-8s %(message)s', @@ -252,7 +252,7 @@ def test(): # test statistics for TCP header = common.pack_addr(b'google.com') + struct.pack('>H', 80) - data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1, + data = cryptor.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', header + b'GET /\r\n\r\n') tcp_cli = socket.socket() tcp_cli.connect(('127.0.0.1', 7001)) @@ -270,7 +270,7 @@ def test(): # 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, + data = cryptor.encrypt_all(b'foobar2', 'aes-256-cfb', header + b'test') udp_cli = socket.socket(type=socket.SOCK_DGRAM) udp_cli.sendto(data, ('127.0.0.1', 8382)) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index b46510c..ce04c1d 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -28,7 +28,7 @@ import traceback from functools import wraps from shadowsocks.common import to_bytes, to_str, IPNetwork -from shadowsocks import encrypt +from shadowsocks import cryptor VERBOSE_LEVEL = 5 @@ -200,7 +200,7 @@ def check_config(config, is_local): if config.get('dns_server', None) is not None: logging.info('Specified DNS server: %s' % config['dns_server']) - encrypt.try_cipher(config['password'], config['method']) + cryptor.try_cipher(config['password'], config['method']) def get_config(is_local): @@ -362,6 +362,18 @@ Proxy options: -l LOCAL_PORT local port, default: 1080 -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb + supported method: + chacha20-poly1305, chacha20-ietf-poly1305, + *xchacha20-ietf-poly1305, + aes-128-gcm, aes-192-gcm, aes-256-gcm, + aes-128-cfb, aes-192-cfb, aes-256-cfb, + es-128-ctr, aes-192-ctr, aes-256-ctr, + camellia-128-cfb, camellia-192-cfb, + camellia-256-cfb, + salsa20, chacha20, chacha20-ietf, + bf-cfb, cast5-cfb, des-cfb, idea-cfb, + rc2-cfb, seed-cfb, + rc4, rc4-md5, table. -t TIMEOUT timeout in seconds, default: 300 -a ONE_TIME_AUTH one time auth --fast-open use TCP_FASTOPEN, requires Linux 3.7+ @@ -392,6 +404,18 @@ Proxy options: -p SERVER_PORT server port, default: 8388 -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb + supported method: + chacha20-poly1305, chacha20-ietf-poly1305, + *xchacha20-ietf-poly1305, + aes-128-gcm, aes-192-gcm, aes-256-gcm, + aes-128-cfb, aes-192-cfb, aes-256-cfb, + es-128-ctr, aes-192-ctr, aes-256-ctr, + camellia-128-cfb, camellia-192-cfb, + camellia-256-cfb, + salsa20, chacha20, chacha20-ietf, + bf-cfb, cast5-cfb, des-cfb, idea-cfb, + rc2-cfb, seed-cfb, + rc4, rc4-md5, table. -t TIMEOUT timeout in seconds, default: 300 -a ONE_TIME_AUTH one time auth --fast-open use TCP_FASTOPEN, requires Linux 3.7+ diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index a61cba7..c57406d 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -26,7 +26,7 @@ import logging import traceback import random -from shadowsocks import encrypt, eventloop, shell, common +from shadowsocks import cryptor, eventloop, shell, common from shadowsocks.common import parse_header, onetimeauth_verify, \ onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \ ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH @@ -94,9 +94,9 @@ WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING BUF_SIZE = 32 * 1024 - # helper exceptions for TCPRelayHandler + class BadSocksHeader(Exception): pass @@ -125,8 +125,8 @@ class TCPRelayHandler(object): # if is_local, this is sslocal self._is_local = is_local self._stage = STAGE_INIT - self._encryptor = encrypt.Encryptor(config['password'], - config['method']) + self._cryptor = cryptor.Cryptor(config['password'], + config['method']) self._ota_enable = config.get('one_time_auth', False) self._ota_enable_session = self._ota_enable self._ota_buff_head = b'' @@ -257,7 +257,7 @@ class TCPRelayHandler(object): return if self._ota_enable_session: data = self._ota_chunk_data_gen(data) - data = self._encryptor.encrypt(data) + data = self._cryptor.encrypt(data) self._data_to_write_to_remote.append(data) if self._config['fast_open'] and not self._fastopen_connected: @@ -347,7 +347,7 @@ class TCPRelayHandler(object): offset = header_length + ONETIMEAUTH_BYTES _hash = data[header_length: offset] _data = data[:header_length] - key = self._encryptor.decipher_iv + self._encryptor.key + key = self._cryptor.decipher_iv + self._cryptor.key if onetimeauth_verify(_hash, _data, key) is False: logging.warn('one time auth fail') self.destroy() @@ -368,11 +368,11 @@ class TCPRelayHandler(object): # ATYP & 0x10 == 0x10, then OTA is enabled. if self._ota_enable_session: data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] - key = self._encryptor.cipher_iv + self._encryptor.key + key = self._cryptor.cipher_iv + self._cryptor.key _header = data[:header_length] sha110 = onetimeauth_gen(data, key) data = _header + sha110 + data[header_length:] - data_to_send = self._encryptor.encrypt(data) + data_to_send = self._cryptor.encrypt(data) self._data_to_write_to_remote.append(data_to_send) # notice here may go into _handle_dns_resolved directly self._dns_resolver.resolve(self._chosen_server[0], @@ -475,7 +475,7 @@ class TCPRelayHandler(object): _hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:] _data = self._ota_buff_data index = struct.pack('>I', self._ota_chunk_idx) - key = self._encryptor.decipher_iv + index + key = self._cryptor.decipher_iv + index if onetimeauth_verify(_hash, _data, key) is False: logging.warn('one time auth fail, drop chunk !') else: @@ -490,7 +490,7 @@ class TCPRelayHandler(object): def _ota_chunk_data_gen(self, data): data_len = struct.pack(">H", len(data)) index = struct.pack('>I', self._ota_chunk_idx) - key = self._encryptor.cipher_iv + index + key = self._cryptor.cipher_iv + index sha110 = onetimeauth_gen(data, key) self._ota_chunk_idx += 1 return data_len + sha110 + data @@ -499,7 +499,7 @@ class TCPRelayHandler(object): if self._is_local: if self._ota_enable_session: data = self._ota_chunk_data_gen(data) - data = self._encryptor.encrypt(data) + data = self._cryptor.encrypt(data) self._write_to_sock(data, self._remote_sock) else: if self._ota_enable_session: @@ -564,7 +564,7 @@ class TCPRelayHandler(object): return self._update_activity(len(data)) if not is_local: - data = self._encryptor.decrypt(data) + data = self._cryptor.decrypt(data) if not data: return if self._stage == STAGE_STREAM: @@ -598,9 +598,9 @@ class TCPRelayHandler(object): return self._update_activity(len(data)) if self._is_local: - data = self._encryptor.decrypt(data) + data = self._cryptor.decrypt(data) else: - data = self._encryptor.encrypt(data) + data = self._cryptor.encrypt(data) try: self._write_to_sock(data, self._local_sock) except Exception as e: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 68e7c55..e966541 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -68,7 +68,7 @@ import struct import errno import random -from shadowsocks import encrypt, eventloop, lru_cache, common, shell +from shadowsocks import cryptor, eventloop, lru_cache, common, shell from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, \ onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH @@ -170,7 +170,7 @@ class UDPRelay(object): else: data = data[3:] else: - data, key, iv = encrypt.decrypt_all(self._password, + data, key, iv = cryptor.decrypt_all(self._password, self._method, data) # decrypt data @@ -234,11 +234,11 @@ class UDPRelay(object): self._eventloop.add(client, eventloop.POLL_IN, self) if self._is_local: - key, iv, m = encrypt.gen_key_iv(self._password, self._method) + key, iv, m = cryptor.gen_key_iv(self._password, self._method) # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._ota_enable_session: data = self._ota_chunk_data_gen(key, iv, data) - data = encrypt.encrypt_all_m(key, iv, m, self._method, data) + data = cryptor.encrypt_all_m(key, iv, m, self._method, data) if not data: return else: @@ -267,13 +267,12 @@ class UDPRelay(object): # drop return data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data - response = encrypt.encrypt_all(self._password, self._method, 1, - data) + response = cryptor.encrypt_all(self._password, self._method, data) if not response: return else: - data = encrypt.encrypt_all(self._password, self._method, 0, - data) + data, key, iv = cryptor.decrypt_all(self._password, + self._method, data) if not data: return header_result = parse_header(data) From d6b40efa5d9522406a710ae68244802160e8aba2 Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Sun, 5 Mar 2017 01:09:40 +0800 Subject: [PATCH 178/196] encrypt.py is renamed to cryptor.py (#780) --- shadowsocks/encrypt.py | 238 ----------------------------------------- 1 file changed, 238 deletions(-) delete mode 100644 shadowsocks/encrypt.py diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py deleted file mode 100644 index ece72ec..0000000 --- a/shadowsocks/encrypt.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012-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 os -import sys -import hashlib -import logging - -from shadowsocks import common -from shadowsocks.crypto import rc4_md5, openssl, sodium, table - - -method_supported = {} -method_supported.update(rc4_md5.ciphers) -method_supported.update(openssl.ciphers) -method_supported.update(sodium.ciphers) -method_supported.update(table.ciphers) - - -def random_string(length): - return os.urandom(length) - - -cached_keys = {} - - -def try_cipher(key, method=None): - Encryptor(key, method) - - -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 - cached_key = '%s-%d-%d' % (password, key_len, iv_len) - r = cached_keys.get(cached_key, None) - if r: - return r - m = [] - i = 0 - while len(b''.join(m)) < (key_len + iv_len): - md5 = hashlib.md5() - data = password - if i > 0: - data = m[i - 1] + password - md5.update(data) - m.append(md5.digest()) - i += 1 - ms = b''.join(m) - key = ms[:key_len] - iv = ms[key_len:key_len + iv_len] - cached_keys[cached_key] = (key, iv) - return key, iv - - -class Encryptor(object): - def __init__(self, password, method): - self.password = password - self.key = None - self.method = method - self.iv_sent = False - self.cipher_iv = b'' - self.decipher = None - self.decipher_iv = None - method = method.lower() - self._method_info = self.get_method_info(method) - if self._method_info: - self.cipher = self.get_cipher(password, method, 1, - random_string(self._method_info[1])) - else: - logging.error('method %s not supported' % method) - sys.exit(1) - - def get_method_info(self, method): - method = method.lower() - m = method_supported.get(method) - return m - - def iv_len(self): - return len(self.cipher_iv) - - def get_cipher(self, password, method, op, iv): - password = common.to_bytes(password) - m = self._method_info - if m[0] > 0: - key, iv_ = EVP_BytesToKey(password, m[0], m[1]) - else: - # key_length == 0 indicates we should use the key directly - key, iv = password, b'' - self.key = key - iv = iv[:m[1]] - if op == 1: - # this iv is for cipher not decipher - self.cipher_iv = iv[:m[1]] - return m[2](method, key, iv, op) - - def encrypt(self, buf): - if len(buf) == 0: - return buf - if self.iv_sent: - return self.cipher.update(buf) - else: - self.iv_sent = True - return self.cipher_iv + self.cipher.update(buf) - - def decrypt(self, buf): - if len(buf) == 0: - return buf - if self.decipher is None: - decipher_iv_len = self._method_info[1] - decipher_iv = buf[:decipher_iv_len] - self.decipher_iv = decipher_iv - self.decipher = self.get_cipher(self.password, self.method, 0, - iv=decipher_iv) - buf = buf[decipher_iv_len:] - if len(buf) == 0: - return buf - return self.decipher.update(buf) - - -def gen_key_iv(password, method): - method = method.lower() - (key_len, iv_len, m) = method_supported[method] - key = None - if key_len > 0: - key, _ = EVP_BytesToKey(password, key_len, iv_len) - else: - key = password - iv = random_string(iv_len) - return key, iv, m - - -def encrypt_all_m(key, iv, m, method, data): - result = [] - result.append(iv) - cipher = m(method, key, iv, 1) - result.append(cipher.update(data)) - return b''.join(result) - - -def dencrypt_all(password, method, data): - result = [] - method = method.lower() - (key_len, iv_len, m) = method_supported[method] - key = None - if key_len > 0: - key, _ = EVP_BytesToKey(password, key_len, iv_len) - else: - key = password - iv = data[:iv_len] - data = data[iv_len:] - cipher = m(method, key, iv, 0) - result.append(cipher.update(data)) - return b''.join(result), key, iv - - -def encrypt_all(password, method, op, data): - result = [] - method = method.lower() - (key_len, iv_len, m) = method_supported[method] - key = None - if key_len > 0: - key, _ = EVP_BytesToKey(password, key_len, iv_len) - else: - key = password - if op: - iv = random_string(iv_len) - result.append(iv) - else: - iv = data[:iv_len] - data = data[iv_len:] - cipher = m(method, key, iv, op) - result.append(cipher.update(data)) - return b''.join(result) - - -CIPHERS_TO_TEST = [ - 'aes-128-cfb', - 'aes-256-cfb', - 'rc4-md5', - 'salsa20', - 'chacha20', - 'table', -] - - -def test_encryptor(): - from os import urandom - plain = urandom(10240) - for method in CIPHERS_TO_TEST: - logging.warn(method) - encryptor = Encryptor(b'key', method) - decryptor = Encryptor(b'key', method) - cipher = encryptor.encrypt(plain) - plain2 = decryptor.decrypt(cipher) - assert plain == plain2 - - -def test_encrypt_all(): - from os import urandom - plain = urandom(10240) - for method in CIPHERS_TO_TEST: - logging.warn(method) - cipher = encrypt_all(b'key', method, 1, plain) - plain2 = encrypt_all(b'key', method, 0, cipher) - assert plain == plain2 - - -def test_encrypt_all_m(): - from os import urandom - plain = urandom(10240) - for method in CIPHERS_TO_TEST: - logging.warn(method) - key, iv, m = gen_key_iv(b'key', method) - cipher = encrypt_all_m(key, iv, m, method, plain) - plain2, key, iv = dencrypt_all(b'key', method, cipher) - assert plain == plain2 - - -if __name__ == '__main__': - test_encrypt_all() - test_encryptor() - test_encrypt_all_m() From 74f8f8cb852201b1f18272e2db2431e7179d32a0 Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Fri, 10 Mar 2017 17:44:42 +0800 Subject: [PATCH 179/196] Add sodium:aes-256-gcm and openssl:ocb AEAD mode (#784) * Add sodium:aes-256-gcm and openssl:ocb AEAD mode Use sodium_increment for nonce_increment if avaiable * Fix pep8 * Fix python3 test code --- shadowsocks/crypto/aead.py | 111 +++++++++++++++++++++++--------- shadowsocks/crypto/openssl.py | 82 ++++++++++++++++++----- shadowsocks/crypto/sodium.py | 118 ++++++++++++++++++++++++++++++---- shadowsocks/crypto/util.py | 20 +++--- shadowsocks/shell.py | 54 +++++++++------- 5 files changed, 295 insertions(+), 90 deletions(-) diff --git a/shadowsocks/crypto/aead.py b/shadowsocks/crypto/aead.py index 1ca06f2..23c0b36 100644 --- a/shadowsocks/crypto/aead.py +++ b/shadowsocks/crypto/aead.py @@ -14,11 +14,12 @@ from __future__ import absolute_import, division, print_function, \ with_statement -from ctypes import create_string_buffer +from ctypes import c_int, create_string_buffer, byref, c_void_p import hashlib from struct import pack, unpack +from shadowsocks.crypto import util from shadowsocks.crypto import hkdf from shadowsocks.common import ord, chr @@ -42,22 +43,58 @@ CIPHER_NONCE_LEN = { 'aes-128-gcm': 12, 'aes-192-gcm': 12, 'aes-256-gcm': 12, + 'aes-128-ocb': 12, # requires openssl 1.1 + 'aes-192-ocb': 12, + 'aes-256-ocb': 12, 'chacha20-poly1305': 12, 'chacha20-ietf-poly1305': 12, 'xchacha20-ietf-poly1305': 24, + 'sodium:aes-256-gcm': 12, } CIPHER_TAG_LEN = { 'aes-128-gcm': 16, 'aes-192-gcm': 16, 'aes-256-gcm': 16, + 'aes-128-ocb': 16, # requires openssl 1.1 + 'aes-192-ocb': 16, + 'aes-256-ocb': 16, 'chacha20-poly1305': 16, 'chacha20-ietf-poly1305': 16, 'xchacha20-ietf-poly1305': 16, + 'sodium:aes-256-gcm': 16, } SUBKEY_INFO = b"ss-subkey" +libsodium = None +sodium_loaded = False + + +def load_sodium(): + """ + Load libsodium helpers for nonce increment + :return: None + """ + global libsodium, sodium_loaded + + libsodium = util.find_library('sodium', 'sodium_increment', + 'libsodium') + if libsodium is None: + return + + if libsodium.sodium_init() < 0: + libsodium = None + return + + libsodium.sodium_increment.restype = c_void_p + libsodium.sodium_increment.argtypes = ( + c_void_p, c_int + ) + + sodium_loaded = True + return + def nonce_increment(nonce, nlen): """ @@ -119,12 +156,28 @@ class AeadCryptoBase(object): self.encrypt_once = self.aead_encrypt self.decrypt_once = self.aead_decrypt + # load libsodium for nonce increment + if not sodium_loaded: + load_sodium() + + def nonce_increment(self): + """ + AEAD ciphers need nonce to be unique per key + TODO: cache and check unique + :return: None + """ + global libsodium, sodium_loaded + if sodium_loaded: + libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) + else: + nonce_increment(self._nonce, self._nlen) + def cipher_ctx_init(self): """ Increase nonce to make it unique for the same key - :return: void + :return: None """ - nonce_increment(self._nonce, self._nlen) + self.nonce_increment() # print("".join("%02x" % ord(b) for b in self._nonce)) def aead_encrypt(self, data): @@ -132,7 +185,7 @@ class AeadCryptoBase(object): Encrypt data with authenticate tag :param data: plain text - :return: cipher text with tag + :return: str [payload][tag] cipher text with tag """ raise Exception("Must implement aead_encrypt method") @@ -141,23 +194,21 @@ class AeadCryptoBase(object): Encrypt a chunk for TCP chunks :param data: str - :return: (str, int) + :return: str [len][tag][payload][tag] """ plen = len(data) - l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2 + # l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2 # network byte order - ctext = self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK)) - if len(ctext) != AEAD_CHUNK_SIZE_LEN + self._tlen: + ctext = [self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))] + if len(ctext[0]) != AEAD_CHUNK_SIZE_LEN + self._tlen: + raise Exception("size length invalid") + + ctext.append(self.aead_encrypt(data)) + if len(ctext[1]) != plen + self._tlen: raise Exception("data length invalid") - self.cipher_ctx_init() - ctext += self.aead_encrypt(data) - if len(ctext) != l: - raise Exception("data length invalid") - - self.cipher_ctx_init() - return ctext, l + return b''.join(ctext) def encrypt(self, data): """ @@ -169,25 +220,24 @@ class AeadCryptoBase(object): """ plen = len(data) if plen <= AEAD_CHUNK_SIZE_MASK: - ctext, _ = self.encrypt_chunk(data) + ctext = self.encrypt_chunk(data) return ctext - ctext, clen = b"", 0 + ctext = [] while plen > 0: mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \ else AEAD_CHUNK_SIZE_MASK - r, l = self.encrypt_chunk(data[:mlen]) - ctext += r - clen += l + c = self.encrypt_chunk(data[:mlen]) + ctext.append(c) data = data[mlen:] plen -= mlen - return ctext + return b''.join(ctext) def aead_decrypt(self, data): """ Decrypt data and authenticate tag - :param data: str cipher text with tag + :param data: str [len][tag][payload][tag] cipher text with tag :return: str plain text """ raise Exception("Must implement aead_decrypt method") @@ -196,7 +246,7 @@ class AeadCryptoBase(object): """ Decrypt chunk size - :param data: str encrypted msg + :param data: str [size][tag] encrypted chunk payload len :return: (int, str) msg length and remaining encrypted data """ if self._chunk['mlen'] > 0: @@ -213,7 +263,6 @@ class AeadCryptoBase(object): if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0: raise Exception('Invalid message length') - self.cipher_ctx_init() return plen, data[hlen:] def decrypt_chunk_payload(self, plen, data): @@ -221,7 +270,7 @@ class AeadCryptoBase(object): Decrypted encrypted msg payload :param plen: int payload length - :param data: str encrypted data + :param data: str [payload][tag][[len][tag]....] encrypted data :return: (str, str) plain text and remaining encrypted data """ data = self._chunk['data'] + data @@ -237,15 +286,13 @@ class AeadCryptoBase(object): if len(plaintext) != plen: raise Exception("plaintext length invalid") - self.cipher_ctx_init() - return plaintext, data[plen + self._tlen:] def decrypt_chunk(self, data): """ Decrypt a TCP chunk - :param data: str encrypted msg + :param data: str [len][tag][payload][tag][[len][tag]...] encrypted msg :return: (str, str) decrypted msg and remaining encrypted data """ plen, data = self.decrypt_chunk_size(data) @@ -261,11 +308,13 @@ class AeadCryptoBase(object): :param data: str :return: str """ - ptext, left = self.decrypt_chunk(data) + ptext = [] + pnext, left = self.decrypt_chunk(data) + ptext.append(pnext) while len(left) > 0: pnext, left = self.decrypt_chunk(left) - ptext += pnext - return ptext + ptext.append(pnext) + return b''.join(ptext) def test_nonce_increment(): diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index 50cdc22..6d4d154 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -23,20 +23,24 @@ from ctypes import c_char_p, c_int, c_long, byref,\ from shadowsocks import common from shadowsocks.crypto import util from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \ - nonce_increment, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG + EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG __all__ = ['ciphers'] libcrypto = None loaded = False +libsodium = None +buf = None buf_size = 2048 +ctx_cleanup = None + CIPHER_ENC_UNCHANGED = -1 def load_openssl(): - global loaded, libcrypto, buf, ctx_cleanup + global loaded, libcrypto, libsodium, buf, ctx_cleanup libcrypto = util.find_library(('crypto', 'eay32'), 'EVP_get_cipherbyname', @@ -140,10 +144,13 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): super(OpenSSLAeadCrypto, self).__init__(cipher_name) AeadCryptoBase.__init__(self, cipher_name, key, iv, op) + key_ptr = c_char_p(self._skey) r = libcrypto.EVP_CipherInit_ex( self._ctx, - self._cipher, None, - None, None, c_int(op) + self._cipher, + None, + key_ptr, None, + c_int(op) ) if not r: self.clean() @@ -165,21 +172,19 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): Need init cipher context after EVP_CipherFinal_ex to reuse context :return: void """ - key_ptr = c_char_p(self._skey) iv_ptr = c_char_p(self._nonce.raw) - r = libcrypto.EVP_CipherInit_ex( self._ctx, - None, None, - key_ptr, iv_ptr, + None, + None, + None, iv_ptr, c_int(CIPHER_ENC_UNCHANGED) ) if not r: self.clean() raise Exception('can not initialize cipher context') - nonce_increment(self._nonce, self._nlen) - # print("".join("%02x" % ord(b) for b in self._nonce)) + AeadCryptoBase.nonce_increment(self) def set_tag(self, tag): """ @@ -225,7 +230,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): ) if not r: # print(self._nonce.raw, r, cipher_out_len) - raise Exception('Verify data failed') + raise Exception('Finalize cipher failed') return buf.raw[:cipher_out_len.value] def aead_encrypt(self, data): @@ -236,6 +241,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): :return: cipher text with tag """ ctext = self.update(data) + self.final() + self.get_tag() + self.cipher_ctx_init() return ctext def aead_decrypt(self, data): @@ -251,6 +257,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): self.set_tag(data[clen - self._tlen:]) plaintext = self.update(data[:clen - self._tlen]) + self.final() + self.cipher_ctx_init() return plaintext @@ -305,6 +312,7 @@ ciphers = { def run_method(method): + print(method, ': [stream]', 32) cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1) decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0) @@ -313,6 +321,13 @@ def run_method(method): def run_aead_method(method, key_len=16): + print(method, ': [payload][tag]', key_len) + cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method)) + if not cipher: + cipher = load_cipher(common.to_bytes(method)) + if not cipher: + print('cipher not avaiable, please upgrade openssl') + return key_len = int(key_len) cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0) @@ -320,18 +335,46 @@ def run_aead_method(method, key_len=16): util.run_cipher(cipher, decipher) +def run_aead_method_chunk(method, key_len=16): + + print(method, ': chunk([size][tag][payload][tag]', key_len) + cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method)) + if not cipher: + cipher = load_cipher(common.to_bytes(method)) + if not cipher: + print('cipher not avaiable, please upgrade openssl') + return + key_len = int(key_len) + cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) + decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0) + + cipher.encrypt_once = cipher.encrypt + decipher.decrypt_once = decipher.decrypt + util.run_cipher(cipher, decipher) + + def test_aes_128_cfb(): run_method('aes-128-cfb') def test_aes_gcm(bits=128): method = "aes-{0}-gcm".format(bits) - print(method, int(bits / 8)) run_aead_method(method, bits / 8) -def test_aes_256_gcm(): - test_aes_gcm(256) +def test_aes_ocb(bits=128): + method = "aes-{0}-ocb".format(bits) + run_aead_method(method, bits / 8) + + +def test_aes_gcm_chunk(bits=128): + method = "aes-{0}-gcm".format(bits) + run_aead_method_chunk(method, bits / 8) + + +def test_aes_ocb_chunk(bits=128): + method = "aes-{0}-ocb".format(bits) + run_aead_method_chunk(method, bits / 8) def test_aes_256_cfb(): @@ -360,7 +403,16 @@ def test_rc4(): if __name__ == '__main__': test_aes_128_cfb() + test_aes_256_cfb() test_aes_gcm(128) test_aes_gcm(192) test_aes_gcm(256) - test_aes_256_gcm() + test_aes_gcm_chunk(128) + test_aes_gcm_chunk(192) + test_aes_gcm_chunk(256) + test_aes_ocb(128) + test_aes_ocb(192) + test_aes_ocb(256) + test_aes_ocb_chunk(128) + test_aes_ocb_chunk(192) + test_aes_ocb_chunk(256) diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index 48a459f..69196a3 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -21,6 +21,7 @@ from ctypes import c_char_p, c_int, c_ulonglong, byref, c_ulong, \ create_string_buffer, c_void_p from shadowsocks.crypto import util +from shadowsocks.crypto import aead from shadowsocks.crypto.aead import AeadCryptoBase __all__ = ['ciphers'] @@ -28,6 +29,7 @@ __all__ = ['ciphers'] libsodium = None loaded = False +buf = None buf_size = 2048 # for salsa20 and chacha20 and chacha20-ietf @@ -37,10 +39,20 @@ BLOCK_SIZE = 64 def load_libsodium(): global loaded, libsodium, buf - libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', - 'libsodium') - if libsodium is None: - raise Exception('libsodium not found') + if not aead.sodium_loaded: + aead.load_sodium() + + if aead.sodium_loaded: + libsodium = aead.libsodium + else: + print('load libsodium again') + libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', + 'libsodium') + if libsodium is None: + raise Exception('libsodium not found') + + if libsodium.sodium_init() < 0: + raise Exception('libsodium init failed') libsodium.crypto_stream_salsa20_xor_ic.restype = c_int libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, @@ -116,10 +128,26 @@ def load_libsodium(): c_char_p, c_char_p ) - libsodium.sodium_increment.restype = c_void_p - libsodium.sodium_increment.argtypes = ( - c_void_p, c_int - ) + # aes-256-gcm, same api structure as above + libsodium.crypto_aead_aes256gcm_is_available.restype = c_int + + if libsodium.crypto_aead_aes256gcm_is_available(): + libsodium.crypto_aead_aes256gcm_encrypt.restype = c_int + libsodium.crypto_aead_aes256gcm_encrypt.argtypes = ( + c_void_p, c_void_p, + c_char_p, c_ulonglong, + c_char_p, c_ulonglong, + c_char_p, + c_char_p, c_char_p + ) + libsodium.crypto_aead_aes256gcm_decrypt.restype = c_int + libsodium.crypto_aead_aes256gcm_decrypt.argtypes = ( + c_void_p, c_void_p, + c_char_p, + c_char_p, c_ulonglong, + c_char_p, c_ulonglong, + c_char_p, c_char_p + ) buf = create_string_buffer(buf_size) loaded = True @@ -192,11 +220,15 @@ class SodiumAeadCrypto(AeadCryptoBase): crypto_aead_xchacha20poly1305_ietf_decrypt else: raise Exception('Unknown cipher') + elif cipher_name == 'sodium:aes-256-gcm': + if hasattr(libsodium, 'crypto_aead_aes256gcm_encrypt'): + self.encryptor = libsodium.crypto_aead_aes256gcm_encrypt + self.decryptor = libsodium.crypto_aead_aes256gcm_decrypt else: raise Exception('Unknown cipher') def cipher_ctx_init(self): - + global libsodium libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) # print("".join("%02x" % ord(b) for b in self._nonce)) @@ -216,6 +248,7 @@ class SodiumAeadCrypto(AeadCryptoBase): if cipher_out_len.value != plen + self._tlen: raise Exception("Encrypt failed") + self.cipher_ctx_init() return buf.raw[:cipher_out_len.value] def aead_decrypt(self, data): @@ -238,6 +271,7 @@ class SodiumAeadCrypto(AeadCryptoBase): if cipher_out_len.value != clen - self._tlen: raise Exception("Encrypt failed") + self.cipher_ctx_init() return buf.raw[:cipher_out_len.value] @@ -248,10 +282,13 @@ ciphers = { 'chacha20-poly1305': (32, 32, SodiumAeadCrypto), 'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), 'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), + 'sodium:aes-256-gcm': (32, 32, SodiumAeadCrypto), } def test_salsa20(): + + print("Test salsa20") cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0) @@ -260,6 +297,7 @@ def test_salsa20(): def test_chacha20(): + print("Test chacha20") cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) @@ -268,6 +306,7 @@ def test_chacha20(): def test_chacha20_ietf(): + print("Test chacha20-ietf") cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) @@ -276,7 +315,7 @@ def test_chacha20_ietf(): def test_chacha20_poly1305(): - print("Test chacha20-poly1305") + print("Test chacha20-poly1305 [payload][tag]") cipher = SodiumAeadCrypto('chacha20-poly1305', b'k' * 32, b'i' * 32, 1) decipher = SodiumAeadCrypto('chacha20-poly1305', @@ -285,9 +324,23 @@ def test_chacha20_poly1305(): util.run_cipher(cipher, decipher) +def test_chacha20_poly1305_chunk(): + + print("Test chacha20-poly1305 chunk [size][tag][payload][tag]") + cipher = SodiumAeadCrypto('chacha20-poly1305', + b'k' * 32, b'i' * 32, 1) + decipher = SodiumAeadCrypto('chacha20-poly1305', + b'k' * 32, b'i' * 32, 0) + + cipher.encrypt_once = cipher.encrypt + decipher.decrypt_once = decipher.decrypt + + util.run_cipher(cipher, decipher) + + def test_chacha20_ietf_poly1305(): - print("Test chacha20-ietf-poly1305") + print("Test chacha20-ietf-poly1305 [payload][tag]") cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', b'k' * 32, b'i' * 32, 1) decipher = SodiumAeadCrypto('chacha20-ietf-poly1305', @@ -296,9 +349,52 @@ def test_chacha20_ietf_poly1305(): util.run_cipher(cipher, decipher) +def test_chacha20_ietf_poly1305_chunk(): + + print("Test chacha20-ietf-poly1305 chunk [size][tag][payload][tag]") + cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', + b'k' * 32, b'i' * 32, 1) + decipher = SodiumAeadCrypto('chacha20-ietf-poly1305', + b'k' * 32, b'i' * 32, 0) + + cipher.encrypt_once = cipher.encrypt + decipher.decrypt_once = decipher.decrypt + + util.run_cipher(cipher, decipher) + + +def test_aes_256_gcm(): + + print("Test sodium:aes-256-gcm [payload][tag]") + cipher = SodiumAeadCrypto('sodium:aes-256-gcm', + b'k' * 32, b'i' * 32, 1) + decipher = SodiumAeadCrypto('sodium:aes-256-gcm', + b'k' * 32, b'i' * 32, 0) + + util.run_cipher(cipher, decipher) + + +def test_aes_256_gcm_chunk(): + + print("Test sodium:aes-256-gcm chunk [size][tag][payload][tag]") + cipher = SodiumAeadCrypto('sodium:aes-256-gcm', + b'k' * 32, b'i' * 32, 1) + decipher = SodiumAeadCrypto('sodium:aes-256-gcm', + b'k' * 32, b'i' * 32, 0) + + cipher.encrypt_once = cipher.encrypt + decipher.decrypt_once = decipher.decrypt + + util.run_cipher(cipher, decipher) + + if __name__ == '__main__': test_chacha20() test_salsa20() test_chacha20_ietf() test_chacha20_poly1305() + test_chacha20_poly1305_chunk() test_chacha20_ietf_poly1305() + test_chacha20_ietf_poly1305_chunk() + test_aes_256_gcm() + test_aes_256_gcm_chunk() diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index bb23197..de389b9 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -114,25 +114,27 @@ def run_cipher(cipher, decipher): rounds = 1 * 1024 plain = urandom(block_size * rounds) - results = [] + cipher_results = [] pos = 0 print('test start') start = time.time() while pos < len(plain): l = random.randint(100, 32768) - c = cipher.encrypt(plain[pos:pos + l]) - results.append(c) + # print(pos, l) + c = cipher.encrypt_once(plain[pos:pos + l]) + cipher_results.append(c) pos += l pos = 0 - c = b''.join(results) - results = [] - while pos < len(c): - l = random.randint(100, 32768) - results.append(decipher.decrypt(c[pos:pos + l])) + # c = b''.join(cipher_results) + plain_results = [] + for c in cipher_results: + # l = random.randint(100, 32768) + l = len(c) + plain_results.append(decipher.decrypt_once(c)) pos += l end = time.time() print('speed: %d bytes/s' % (block_size * rounds / (end - start))) - assert b''.join(results) == plain + assert b''.join(plain_results) == plain def test_find_library(): diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index ce04c1d..1a7322c 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -362,18 +362,21 @@ Proxy options: -l LOCAL_PORT local port, default: 1080 -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb - supported method: - chacha20-poly1305, chacha20-ietf-poly1305, - *xchacha20-ietf-poly1305, - aes-128-gcm, aes-192-gcm, aes-256-gcm, - aes-128-cfb, aes-192-cfb, aes-256-cfb, - es-128-ctr, aes-192-ctr, aes-256-ctr, - camellia-128-cfb, camellia-192-cfb, - camellia-256-cfb, - salsa20, chacha20, chacha20-ietf, - bf-cfb, cast5-cfb, des-cfb, idea-cfb, - rc2-cfb, seed-cfb, - rc4, rc4-md5, table. + Sodium: + chacha20-poly1305, chacha20-ietf-poly1305, + *xchacha20-ietf-poly1305, + sodium:aes-256-gcm, + salsa20, chacha20, chacha20-ietf. + OpenSSL:(* v1.1) + *aes-128-ocb, *aes-192-ocb, *aes-256-ocb, + aes-128-gcm, aes-192-gcm, aes-256-gcm, + aes-128-cfb, aes-192-cfb, aes-256-cfb, + aes-128-ctr, aes-192-ctr, aes-256-ctr, + camellia-128-cfb, camellia-192-cfb, + camellia-256-cfb, + bf-cfb, cast5-cfb, des-cfb, idea-cfb, + rc2-cfb, seed-cfb, + rc4, rc4-md5, table. -t TIMEOUT timeout in seconds, default: 300 -a ONE_TIME_AUTH one time auth --fast-open use TCP_FASTOPEN, requires Linux 3.7+ @@ -404,18 +407,21 @@ Proxy options: -p SERVER_PORT server port, default: 8388 -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb - supported method: - chacha20-poly1305, chacha20-ietf-poly1305, - *xchacha20-ietf-poly1305, - aes-128-gcm, aes-192-gcm, aes-256-gcm, - aes-128-cfb, aes-192-cfb, aes-256-cfb, - es-128-ctr, aes-192-ctr, aes-256-ctr, - camellia-128-cfb, camellia-192-cfb, - camellia-256-cfb, - salsa20, chacha20, chacha20-ietf, - bf-cfb, cast5-cfb, des-cfb, idea-cfb, - rc2-cfb, seed-cfb, - rc4, rc4-md5, table. + Sodium: + chacha20-poly1305, chacha20-ietf-poly1305, + *xchacha20-ietf-poly1305, + sodium:aes-256-gcm, + salsa20, chacha20, chacha20-ietf. + OpenSSL:(* v1.1) + *aes-128-ocb, *aes-192-ocb, *aes-256-ocb, + aes-128-gcm, aes-192-gcm, aes-256-gcm, + aes-128-cfb, aes-192-cfb, aes-256-cfb, + aes-128-ctr, aes-192-ctr, aes-256-ctr, + camellia-128-cfb, camellia-192-cfb, + camellia-256-cfb, + bf-cfb, cast5-cfb, des-cfb, idea-cfb, + rc2-cfb, seed-cfb, + rc4, rc4-md5, table. -t TIMEOUT timeout in seconds, default: 300 -a ONE_TIME_AUTH one time auth --fast-open use TCP_FASTOPEN, requires Linux 3.7+ From 4e21f83bd66287c54d15c08d5a85d47d68777c92 Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Wed, 15 Mar 2017 00:35:17 +0800 Subject: [PATCH 180/196] Handle cipher exceptions #783 (#791) --- shadowsocks/tcprelay.py | 1 + shadowsocks/tunnel.py | 0 shadowsocks/udprelay.py | 35 +++++++++++++++++++++++++---------- 3 files changed, 26 insertions(+), 10 deletions(-) mode change 100644 => 100755 shadowsocks/tunnel.py diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index c57406d..ea85e4d 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -641,6 +641,7 @@ class TCPRelayHandler(object): logging.error(eventloop.get_sock_error(self._remote_sock)) self.destroy() + @shell.exception_handle(self_=True, destroy=True) def handle_event(self, sock, event): # handle all events in this handler and dispatch them to methods if self._stage == STAGE_DESTROYED: diff --git a/shadowsocks/tunnel.py b/shadowsocks/tunnel.py old mode 100644 new mode 100755 diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index e966541..38af1b6 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -170,14 +170,16 @@ class UDPRelay(object): else: data = data[3:] else: - data, key, iv = cryptor.decrypt_all(self._password, - self._method, - data) # decrypt data + try: + data, key, iv = cryptor.decrypt_all(self._password, + self._method, + data) + except Exception: + logging.debug('UDP handle_server: decrypt data failed') + return if not data: - logging.debug( - 'UDP handle_server: data is empty after decrypt' - ) + logging.debug('UDP handle_server: data is empty after decrypt') return header_result = parse_header(data) if header_result is None: @@ -238,7 +240,11 @@ class UDPRelay(object): # spec https://shadowsocks.org/en/spec/one-time-auth.html if self._ota_enable_session: data = self._ota_chunk_data_gen(key, iv, data) - data = cryptor.encrypt_all_m(key, iv, m, self._method, data) + try: + data = cryptor.encrypt_all_m(key, iv, m, self._method, data) + except Exception: + logging.debug("UDP handle_server: encrypt data failed") + return if not data: return else: @@ -267,12 +273,21 @@ class UDPRelay(object): # drop return data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data - response = cryptor.encrypt_all(self._password, self._method, data) + try: + response = cryptor.encrypt_all(self._password, + self._method, data) + except Exception: + logging.debug("UDP handle_client: encrypt data failed") + return if not response: return else: - data, key, iv = cryptor.decrypt_all(self._password, - self._method, data) + try: + data, key, iv = cryptor.decrypt_all(self._password, + self._method, data) + except Exception: + logging.debug('UDP handle_client: decrypt data failed') + return if not data: return header_result = parse_header(data) From 9e25cc6bb44836f39eabd25e677b066a8551972f Mon Sep 17 00:00:00 2001 From: Falseen Date: Sun, 19 Mar 2017 12:37:57 +0800 Subject: [PATCH 181/196] fix some error for parse dns_servre in config (#798) and fix pep8 https://github.com/shadowsocks/shadowsocks/pull/739 --- config.json.example | 1 + shadowsocks/shell.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index 4006656..2cee3b8 100644 --- a/config.json.example +++ b/config.json.example @@ -7,6 +7,7 @@ "method":"aes-256-cfb", "local_address":"127.0.0.1", "fast_open":false, + "dns_server":["8.8.8.8", 8.8.4.4], "tunnel_remote":"8.8.8.8", "tunnel_remote_port":53, "tunnel_port":53 diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 1a7322c..af33456 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -167,8 +167,7 @@ def check_config(config, is_local): config['server_port'] = int(config['server_port']) if 'tunnel_remote_port' in config: - config['tunnel_remote_port'] = \ - int(config['tunnel_remote_port']) + config['tunnel_remote_port'] = int(config['tunnel_remote_port']) if 'tunnel_port' in config: config['tunnel_port'] = int(config['tunnel_port']) @@ -198,6 +197,8 @@ def check_config(config, is_local): logging.error('user can be used only on Unix') sys.exit(1) if config.get('dns_server', None) is not None: + if type(config['dns_server']) != list: + config['dns_server'] = to_str(config['dns_server']) logging.info('Specified DNS server: %s' % config['dns_server']) cryptor.try_cipher(config['password'], config['method']) @@ -313,8 +314,7 @@ def get_config(is_local): config['prefer_ipv6'] = config.get('prefer_ipv6', False) config['server_port'] = config.get('server_port', 8388) - config['tunnel_remote'] = \ - to_str(config.get('tunnel_remote', '8.8.8.8')) + config['tunnel_remote'] = to_str(config.get('tunnel_remote', '8.8.8.8')) config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53) config['tunnel_port'] = config.get('tunnel_port', 53) config['dns_server'] = config.get('dns_server', None) From 1222fb19a6b8712c142ad38840c68a1b237faae2 Mon Sep 17 00:00:00 2001 From: Falseen Date: Tue, 21 Mar 2017 20:15:38 +0800 Subject: [PATCH 182/196] update find_lib's name for the new openssl-1.1 dll (just for windows) (#796) the new openssl's dll(1.1) is rename to libcrypto-1_1-x64.dll and libcrypto-1_1.dll --- shadowsocks/crypto/util.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index de389b9..cf20e24 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -26,6 +26,7 @@ def find_library_nt(name): # ctypes.util.find_library just returns first result he found # but we want to try them all # because on Windows, users may have both 32bit and 64bit version installed + import glob results = [] for directory in os.environ['PATH'].split(os.pathsep): fname = os.path.join(directory, name) @@ -33,9 +34,10 @@ def find_library_nt(name): results.append(fname) if fname.lower().endswith(".dll"): continue - fname += ".dll" - if os.path.isfile(fname): - results.append(fname) + fname += "*.dll" + files = glob.glob(fname) + if files: + results.extend(files) return results From 0f4e3fa00cad2c6419be1a3b606b8fc7969eb38a Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Sat, 29 Apr 2017 14:27:55 +0800 Subject: [PATCH 183/196] Add mbedtls wrapper, custom crypto lib path, test files (#803) * add mbedtls crypto wrapper. add tests files for new aead ciphers add custom lib path support fix some typo * fix forbidden ip list * rm crypto lib build files * remove crypto source * add xchacha20 test config * convert dos new line format to unix format * Fix help msg --- .travis.yml | 2 + config.json.example | 7 +- shadowsocks/crypto/aead.py | 15 +- shadowsocks/crypto/mbedtls.py | 481 +++++++++++++++++++++++++++++ shadowsocks/crypto/openssl.py | 45 +-- shadowsocks/crypto/rc4_md5.py | 5 +- shadowsocks/crypto/sodium.py | 131 ++++---- shadowsocks/crypto/table.py | 2 +- shadowsocks/crypto/util.py | 35 ++- shadowsocks/cryptor.py | 34 +- shadowsocks/manager.py | 1 + shadowsocks/shell.py | 86 ++++-- shadowsocks/tcprelay.py | 17 +- shadowsocks/udprelay.py | 13 +- tests/aes-cfb1.json | 20 +- tests/aes-cfb8.json | 20 +- tests/aes-ctr.json | 20 +- tests/aes-gcm.json | 10 + tests/aes-ocb.json | 11 + tests/aes-ofb.json | 10 + tests/aes.json | 20 +- tests/camellia.json | 10 + tests/chacha20-ietf-poly1305.json | 10 + tests/chacha20-ietf.json | 20 +- tests/chacha20-poly1305.json | 10 + tests/chacha20.json | 20 +- tests/client-multi-server-ip.json | 20 +- tests/fastopen.json | 20 +- tests/graceful.json | 20 +- tests/ipv6-client-side.json | 20 +- tests/ipv6.json | 20 +- tests/jenkins.sh | 22 ++ tests/libmbedtls/install.sh | 12 + tests/libopenssl/install.sh | 12 + tests/libsodium/install.sh | 9 +- tests/mbedtls-aes-ctr.json | 10 + tests/mbedtls-aes-gcm.json | 10 + tests/mbedtls-aes.json | 10 + tests/mbedtls-camellia.json | 10 + tests/rc4-md5-ota.json | 22 +- tests/rc4-md5.json | 20 +- tests/salsa20-ctr.json | 20 +- tests/salsa20.json | 20 +- tests/server-dnsserver.json | 22 +- tests/table.json | 20 +- tests/workers.json | 20 +- tests/xchacha20-ietf-poly1305.json | 10 + tests/xchacha20.json | 10 + 48 files changed, 1088 insertions(+), 326 deletions(-) create mode 100644 shadowsocks/crypto/mbedtls.py create mode 100644 tests/aes-gcm.json create mode 100644 tests/aes-ocb.json create mode 100644 tests/aes-ofb.json create mode 100644 tests/camellia.json create mode 100644 tests/chacha20-ietf-poly1305.json create mode 100644 tests/chacha20-poly1305.json create mode 100755 tests/libmbedtls/install.sh create mode 100755 tests/libopenssl/install.sh create mode 100644 tests/mbedtls-aes-ctr.json create mode 100644 tests/mbedtls-aes-gcm.json create mode 100644 tests/mbedtls-aes.json create mode 100644 tests/mbedtls-camellia.json create mode 100644 tests/xchacha20-ietf-poly1305.json create mode 100644 tests/xchacha20.json diff --git a/.travis.yml b/.travis.yml index 535bc9a..dda0661 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ before_install: - pip install pep8 pyflakes nose coverage PySocks - sudo tests/socksify/install.sh - sudo tests/libsodium/install.sh + - sudo tests/libmbedtls/install.sh + - sudo tests/libopenssl/install.sh - sudo tests/setup_tc.sh script: - tests/jenkins.sh diff --git a/config.json.example b/config.json.example index 2cee3b8..a1f9125 100644 --- a/config.json.example +++ b/config.json.example @@ -7,8 +7,11 @@ "method":"aes-256-cfb", "local_address":"127.0.0.1", "fast_open":false, - "dns_server":["8.8.8.8", 8.8.4.4], "tunnel_remote":"8.8.8.8", + "dns_server":["8.8.8.8", "8.8.4.4"], "tunnel_remote_port":53, - "tunnel_port":53 + "tunnel_port":53, + "libopenssl":"C:\\Program Files\\Git\\mingw64\\bin\\libeay32.dll", + "libsodium":"/usr/local/lib/libsodium.so", + "libmbedtls":"/usr/local/lib/libmbedcrypto.2.4.0.dylib" } diff --git a/shadowsocks/crypto/aead.py b/shadowsocks/crypto/aead.py index 23c0b36..d800f36 100644 --- a/shadowsocks/crypto/aead.py +++ b/shadowsocks/crypto/aead.py @@ -71,7 +71,7 @@ libsodium = None sodium_loaded = False -def load_sodium(): +def load_sodium(path=None): """ Load libsodium helpers for nonce increment :return: None @@ -79,12 +79,14 @@ def load_sodium(): global libsodium, sodium_loaded libsodium = util.find_library('sodium', 'sodium_increment', - 'libsodium') + 'libsodium', path) if libsodium is None: + print('load libsodium failed with path %s' % path) return if libsodium.sodium_init() < 0: libsodium = None + print('sodium init failed') return libsodium.sodium_increment.restype = c_void_p @@ -139,7 +141,7 @@ class AeadCryptoBase(object): +--------+-----------+-----------+ """ - def __init__(self, cipher_name, key, iv, op): + def __init__(self, cipher_name, key, iv, op, crypto_path=None): self._op = int(op) self._salt = iv self._nlen = CIPHER_NONCE_LEN[cipher_name] @@ -158,7 +160,9 @@ class AeadCryptoBase(object): # load libsodium for nonce increment if not sodium_loaded: - load_sodium() + crypto_path = dict(crypto_path) if crypto_path else dict() + path = crypto_path.get('sodium', None) + load_sodium(path) def nonce_increment(self): """ @@ -171,6 +175,7 @@ class AeadCryptoBase(object): libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) else: nonce_increment(self._nonce, self._nlen) + # print("".join("%02x" % ord(b) for b in self._nonce)) def cipher_ctx_init(self): """ @@ -178,7 +183,6 @@ class AeadCryptoBase(object): :return: None """ self.nonce_increment() - # print("".join("%02x" % ord(b) for b in self._nonce)) def aead_encrypt(self, data): """ @@ -331,4 +335,5 @@ def test_nonce_increment(): if __name__ == '__main__': + load_sodium() test_nonce_increment() diff --git a/shadowsocks/crypto/mbedtls.py b/shadowsocks/crypto/mbedtls.py new file mode 100644 index 0000000..b87c2eb --- /dev/null +++ b/shadowsocks/crypto/mbedtls.py @@ -0,0 +1,481 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Void Copyright NO ONE +# +# Void License +# +# The code belongs to no one. Do whatever you want. +# Forget about boring open source license. +# +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + + +from __future__ import absolute_import, division, print_function, \ + with_statement + +from ctypes import c_char_p, c_int, c_size_t, byref,\ + create_string_buffer, c_void_p + +from shadowsocks import common +from shadowsocks.crypto import util +from shadowsocks.crypto.aead import AeadCryptoBase + +__all__ = ['ciphers'] + +libmbedtls = None +loaded = False + +buf = None +buf_size = 2048 + +CIPHER_ENC_UNCHANGED = -1 + +# define MAX_KEY_LENGTH 64 +# define MAX_NONCE_LENGTH 32 +# typedef struct { +# uint32_t init; +# uint64_t counter; +# cipher_evp_t *evp; +# cipher_t *cipher; +# buffer_t *chunk; +# uint8_t salt[MAX_KEY_LENGTH]; +# uint8_t skey[MAX_KEY_LENGTH]; +# uint8_t nonce[MAX_NONCE_LENGTH]; +# } cipher_ctx_t; +# +# sizeof(cipher_ctx_t) = 196 + +CIPHER_CTX_SIZE = 256 + + +def load_mbedtls(crypto_path=None): + global loaded, libmbedtls, buf + + crypto_path = dict(crypto_path) if crypto_path else dict() + path = crypto_path.get('mbedtls', None) + libmbedtls = util.find_library('mbedcrypto', + 'mbedtls_cipher_init', + 'libmbedcrypto', path) + if libmbedtls is None: + raise Exception('libmbedcrypto(mbedtls) not found with path %s' + % path) + + libmbedtls.mbedtls_cipher_init.restype = None + libmbedtls.mbedtls_cipher_free.restype = None + + libmbedtls.mbedtls_cipher_info_from_string.restype = c_void_p + libmbedtls.mbedtls_cipher_info_from_string.argtypes = (c_char_p,) + + libmbedtls.mbedtls_cipher_setup.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_setup.argtypes = (c_void_p, c_void_p) + + libmbedtls.mbedtls_cipher_setkey.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_setkey.argtypes = ( + c_void_p, # ctx + c_char_p, # key + c_int, # key_bitlen, not bytes + c_int # op: 1 enc, 0 dec, -1 none + ) + + libmbedtls.mbedtls_cipher_set_iv.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_set_iv.argtypes = ( + c_void_p, # ctx + c_char_p, # iv + c_size_t # iv_len + ) + + libmbedtls.mbedtls_cipher_reset.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_reset.argtypes = (c_void_p,) # ctx + + if hasattr(libmbedtls, 'mbedtls_cipher_update_ad'): + libmbedtls.mbedtls_cipher_update_ad.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_update_ad.argtypes = ( + c_void_p, # ctx + c_char_p, # ad + c_size_t # ad_len + ) + + libmbedtls.mbedtls_cipher_update.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_update.argtypes = ( + c_void_p, # ctx + c_char_p, # input + c_size_t, # ilen, must be multiple of block size except last one + c_void_p, # *output + c_void_p # *olen + ) + + libmbedtls.mbedtls_cipher_finish.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_finish.argtypes = ( + c_void_p, # ctx + c_void_p, # *output + c_void_p # *olen + ) + + if hasattr(libmbedtls, 'mbedtls_cipher_write_tag'): + libmbedtls.mbedtls_cipher_write_tag.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_write_tag.argtypes = ( + c_void_p, # ctx + c_void_p, # *tag + c_size_t # tag_len + ) + libmbedtls.mbedtls_cipher_check_tag.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_check_tag.argtypes = ( + c_void_p, # ctx + c_char_p, # tag + c_size_t # tag_len + ) + + libmbedtls.mbedtls_cipher_crypt.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_crypt.argtypes = ( + c_void_p, # ctx + c_char_p, # iv + c_size_t, # iv_len, = 0 if iv = NULL + c_char_p, # input + c_size_t, # ilen + c_void_p, # *output, no less than ilen + block_size + c_void_p # *olen + ) + + if hasattr(libmbedtls, 'mbedtls_cipher_auth_encrypt'): + libmbedtls.mbedtls_cipher_auth_encrypt.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_auth_encrypt.argtypes = ( + c_void_p, # ctx + c_char_p, # iv + c_size_t, # iv_len + c_char_p, # ad + c_size_t, # ad_len + c_char_p, # input + c_size_t, # ilen + c_void_p, # *output, no less than ilen + block_size + c_void_p, # *olen + c_void_p, # *tag + c_size_t # tag_len + ) + libmbedtls.mbedtls_cipher_auth_decrypt.restype = c_int # 0 on success + libmbedtls.mbedtls_cipher_auth_decrypt.argtypes = ( + c_void_p, # ctx + c_char_p, # iv + c_size_t, # iv_len + c_char_p, # ad + c_size_t, # ad_len + c_char_p, # input + c_size_t, # ilen + c_void_p, # *output, no less than ilen + block_size + c_void_p, # *olen + c_char_p, # tag + c_size_t, # tag_len + ) + + buf = create_string_buffer(buf_size) + loaded = True + + +class MbedTLSCryptoBase(object): + """ + MbedTLS crypto base class + """ + def __init__(self, cipher_name, crypto_path=None): + global loaded + self._ctx = create_string_buffer(b'\0' * CIPHER_CTX_SIZE) + self._cipher = None + if not loaded: + load_mbedtls(crypto_path) + cipher_name = common.to_bytes(cipher_name.upper()) + cipher = libmbedtls.mbedtls_cipher_info_from_string(cipher_name) + if not cipher: + raise Exception('cipher %s not found in libmbedtls' % cipher_name) + libmbedtls.mbedtls_cipher_init(byref(self._ctx)) + if libmbedtls.mbedtls_cipher_setup(byref(self._ctx), cipher): + raise Exception('can not setup cipher') + self._cipher = cipher + + self.encrypt_once = self.update + self.decrypt_once = self.update + + def update(self, data): + """ + Encrypt/decrypt data + :param data: str + :return: str + """ + global buf_size, buf + cipher_out_len = c_size_t(0) + l = len(data) + if buf_size < l: + buf_size = l * 2 + buf = create_string_buffer(buf_size) + libmbedtls.mbedtls_cipher_update( + byref(self._ctx), + c_char_p(data), c_size_t(l), + byref(buf), byref(cipher_out_len) + ) + # buf is copied to a str object when we access buf.raw + return buf.raw[:cipher_out_len.value] + + def __del__(self): + self.clean() + + def clean(self): + if self._ctx: + libmbedtls.mbedtls_cipher_free(byref(self._ctx)) + + +class MbedTLSAeadCrypto(MbedTLSCryptoBase, AeadCryptoBase): + """ + Implement mbedtls Aead mode: gcm + """ + def __init__(self, cipher_name, key, iv, op, crypto_path=None): + if cipher_name[:len('mbedtls:')] == 'mbedtls:': + cipher_name = cipher_name[len('mbedtls:'):] + MbedTLSCryptoBase.__init__(self, cipher_name, crypto_path) + AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path) + + key_ptr = c_char_p(self._skey) + r = libmbedtls.mbedtls_cipher_setkey( + byref(self._ctx), + key_ptr, c_int(len(key) * 8), + c_int(op) + ) + if r: + self.clean() + raise Exception('can not initialize cipher context') + + r = libmbedtls.mbedtls_cipher_reset(byref(self._ctx)) + if r: + self.clean() + raise Exception('can not finish preparation of mbed TLS ' + 'cipher context') + + def cipher_ctx_init(self): + """ + Nonce + 1 + :return: None + """ + AeadCryptoBase.nonce_increment(self) + + def set_tag(self, tag): + """ + Set tag before decrypt any data (update) + :param tag: authenticated tag + :return: None + """ + tag_len = self._tlen + r = libmbedtls.mbedtls_cipher_check_tag( + byref(self._ctx), + c_char_p(tag), c_size_t(tag_len) + ) + if not r: + raise Exception('Set tag failed') + + def get_tag(self): + """ + Get authenticated tag, called after EVP_CipherFinal_ex + :return: str + """ + tag_len = self._tlen + tag_buf = create_string_buffer(tag_len) + r = libmbedtls.mbedtls_cipher_write_tag( + byref(self._ctx), + byref(tag_buf), c_size_t(tag_len) + ) + if not r: + raise Exception('Get tag failed') + return tag_buf.raw[:tag_len] + + def final(self): + """ + Finish encrypt/decrypt a chunk (<= 0x3FFF) + :return: str + """ + global buf_size, buf + cipher_out_len = c_size_t(0) + r = libmbedtls.mbedtls_cipher_finish( + byref(self._ctx), + byref(buf), byref(cipher_out_len) + ) + if not r: + # print(self._nonce.raw, r, cipher_out_len) + raise Exception('Finalize cipher failed') + return buf.raw[:cipher_out_len.value] + + def aead_encrypt(self, data): + """ + Encrypt data with authenticate tag + + :param data: plain text + :return: cipher text with tag + """ + global buf_size, buf + plen = len(data) + if buf_size < plen + self._tlen: + buf_size = (plen + self._tlen) * 2 + buf = create_string_buffer(buf_size) + cipher_out_len = c_size_t(0) + tag_buf = create_string_buffer(self._tlen) + + r = libmbedtls.mbedtls_cipher_auth_encrypt( + byref(self._ctx), + c_char_p(self._nonce.raw), c_size_t(self._nlen), + None, c_size_t(0), + c_char_p(data), c_size_t(plen), + byref(buf), byref(cipher_out_len), + byref(tag_buf), c_size_t(self._tlen) + ) + assert cipher_out_len.value == plen + if r: + raise Exception('AEAD encrypt failed {0:#x}'.format(r)) + self.cipher_ctx_init() + return buf.raw[:cipher_out_len.value] + tag_buf.raw[:self._tlen] + + def aead_decrypt(self, data): + """ + Decrypt data and authenticate tag + + :param data: cipher text with tag + :return: plain text + """ + global buf_size, buf + cipher_out_len = c_size_t(0) + plen = len(data) - self._tlen + if buf_size < plen: + buf_size = plen * 2 + buf = create_string_buffer(buf_size) + tag = data[plen:] + r = libmbedtls.mbedtls_cipher_auth_decrypt( + byref(self._ctx), + c_char_p(self._nonce.raw), c_size_t(self._nlen), + None, c_size_t(0), + c_char_p(data), c_size_t(plen), + byref(buf), byref(cipher_out_len), + c_char_p(tag), c_size_t(self._tlen) + ) + if r: + raise Exception('AEAD encrypt failed {0:#x}'.format(r)) + self.cipher_ctx_init() + return buf.raw[:cipher_out_len.value] + + +class MbedTLSStreamCrypto(MbedTLSCryptoBase): + """ + Crypto for stream modes: cfb, ofb, ctr + """ + def __init__(self, cipher_name, key, iv, op, crypto_path=None): + if cipher_name[:len('mbedtls:')] == 'mbedtls:': + cipher_name = cipher_name[len('mbedtls:'):] + MbedTLSCryptoBase.__init__(self, cipher_name, crypto_path) + key_ptr = c_char_p(key) + iv_ptr = c_char_p(iv) + r = libmbedtls.mbedtls_cipher_setkey( + byref(self._ctx), + key_ptr, c_int(len(key) * 8), + c_int(op) + ) + if r: + self.clean() + raise Exception('can not set cipher key') + r = libmbedtls.mbedtls_cipher_set_iv( + byref(self._ctx), + iv_ptr, c_size_t(len(iv)) + ) + if r: + self.clean() + raise Exception('can not set cipher iv') + r = libmbedtls.mbedtls_cipher_reset(byref(self._ctx)) + if r: + self.clean() + raise Exception('can not reset cipher') + + self.encrypt = self.update + self.decrypt = self.update + + +ciphers = { + 'mbedtls:aes-128-cfb128': (16, 16, MbedTLSStreamCrypto), + 'mbedtls:aes-192-cfb128': (24, 16, MbedTLSStreamCrypto), + 'mbedtls:aes-256-cfb128': (32, 16, MbedTLSStreamCrypto), + 'mbedtls:aes-128-ctr': (16, 16, MbedTLSStreamCrypto), + 'mbedtls:aes-192-ctr': (24, 16, MbedTLSStreamCrypto), + 'mbedtls:aes-256-ctr': (32, 16, MbedTLSStreamCrypto), + 'mbedtls:camellia-128-cfb128': (16, 16, MbedTLSStreamCrypto), + 'mbedtls:camellia-192-cfb128': (24, 16, MbedTLSStreamCrypto), + 'mbedtls:camellia-256-cfb128': (32, 16, MbedTLSStreamCrypto), + # AEAD: iv_len = salt_len = key_len + 'mbedtls:aes-128-gcm': (16, 16, MbedTLSAeadCrypto), + 'mbedtls:aes-192-gcm': (24, 24, MbedTLSAeadCrypto), + 'mbedtls:aes-256-gcm': (32, 32, MbedTLSAeadCrypto), +} + + +def run_method(method): + from shadowsocks.crypto import openssl + + print(method, ': [stream]', 32) + cipher = MbedTLSStreamCrypto(method, b'k' * 32, b'i' * 16, 1) + decipher = openssl.OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + +def run_aead_method(method, key_len=16): + from shadowsocks.crypto import openssl + + print(method, ': [payload][tag]', key_len) + key_len = int(key_len) + cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) + decipher = openssl.OpenSSLAeadCrypto( + method, + b'k' * key_len, b'i' * key_len, 0 + ) + + util.run_cipher(cipher, decipher) + + +def run_aead_method_chunk(method, key_len=16): + from shadowsocks.crypto import openssl + + print(method, ': chunk([size][tag][payload][tag]', key_len) + key_len = int(key_len) + cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) + decipher = openssl.OpenSSLAeadCrypto( + method, + b'k' * key_len, b'i' * key_len, 0 + ) + + cipher.encrypt_once = cipher.encrypt + decipher.decrypt_once = decipher.decrypt + util.run_cipher(cipher, decipher) + + +def test_camellia_256_cfb(): + run_method('camellia-256-cfb128') + + +def test_aes_gcm(bits=128): + method = "aes-{0}-gcm".format(bits) + run_aead_method(method, bits / 8) + + +def test_aes_gcm_chunk(bits=128): + method = "aes-{0}-gcm".format(bits) + run_aead_method_chunk(method, bits / 8) + + +def test_aes_256_cfb(): + run_method('aes-256-cfb128') + + +def test_aes_256_ctr(): + run_method('aes-256-ctr') + + +if __name__ == '__main__': + test_aes_256_cfb() + test_camellia_256_cfb() + test_aes_256_ctr() + test_aes_gcm(128) + test_aes_gcm(192) + test_aes_gcm(256) + test_aes_gcm_chunk(128) + test_aes_gcm_chunk(192) + test_aes_gcm_chunk(256) diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index 6d4d154..7f7eef2 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -39,14 +39,16 @@ ctx_cleanup = None CIPHER_ENC_UNCHANGED = -1 -def load_openssl(): +def load_openssl(crypto_path=None): global loaded, libcrypto, libsodium, buf, ctx_cleanup + crypto_path = dict(crypto_path) if crypto_path else dict() + path = crypto_path.get('openssl', None) libcrypto = util.find_library(('crypto', 'eay32'), 'EVP_get_cipherbyname', - 'libcrypto') + 'libcrypto', path) if libcrypto is None: - raise Exception('libcrypto(OpenSSL) not found') + raise Exception('libcrypto(OpenSSL) not found with path %s' % path) libcrypto.EVP_get_cipherbyname.restype = c_void_p libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p @@ -89,11 +91,11 @@ class OpenSSLCryptoBase(object): """ OpenSSL crypto base class """ - def __init__(self, cipher_name): + def __init__(self, cipher_name, crypto_path=None): self._ctx = None self._cipher = None if not loaded: - load_openssl() + load_openssl(crypto_path) cipher_name = common.to_bytes(cipher_name) cipher = libcrypto.EVP_get_cipherbyname(cipher_name) if not cipher: @@ -140,9 +142,9 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): """ Implement OpenSSL Aead mode: gcm, ocb """ - def __init__(self, cipher_name, key, iv, op): - super(OpenSSLAeadCrypto, self).__init__(cipher_name) - AeadCryptoBase.__init__(self, cipher_name, key, iv, op) + def __init__(self, cipher_name, key, iv, op, crypto_path=None): + OpenSSLCryptoBase.__init__(self, cipher_name, crypto_path) + AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path) key_ptr = c_char_p(self._skey) r = libcrypto.EVP_CipherInit_ex( @@ -170,7 +172,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): def cipher_ctx_init(self): """ Need init cipher context after EVP_CipherFinal_ex to reuse context - :return: void + :return: None """ iv_ptr = c_char_p(self._nonce.raw) r = libcrypto.EVP_CipherInit_ex( @@ -190,7 +192,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): """ Set tag before decrypt any data (update) :param tag: authenticated tag - :return: void + :return: None """ tag_len = self._tlen r = libcrypto.EVP_CIPHER_CTX_ctrl( @@ -265,8 +267,8 @@ class OpenSSLStreamCrypto(OpenSSLCryptoBase): """ Crypto for stream modes: cfb, ofb, ctr """ - def __init__(self, cipher_name, key, iv, op): - super(OpenSSLStreamCrypto, self).__init__(cipher_name) + def __init__(self, cipher_name, key, iv, op, crypto_path=None): + OpenSSLCryptoBase.__init__(self, cipher_name, crypto_path) key_ptr = c_char_p(key) iv_ptr = c_char_p(iv) r = libcrypto.EVP_CipherInit_ex(self._ctx, self._cipher, None, @@ -282,9 +284,6 @@ ciphers = { 'aes-128-cfb': (16, 16, OpenSSLStreamCrypto), 'aes-192-cfb': (24, 16, OpenSSLStreamCrypto), 'aes-256-cfb': (32, 16, OpenSSLStreamCrypto), - 'aes-128-gcm': (16, 16, OpenSSLAeadCrypto), - 'aes-192-gcm': (24, 24, OpenSSLAeadCrypto), - 'aes-256-gcm': (32, 32, OpenSSLAeadCrypto), 'aes-128-ofb': (16, 16, OpenSSLStreamCrypto), 'aes-192-ofb': (24, 16, OpenSSLStreamCrypto), 'aes-256-ofb': (32, 16, OpenSSLStreamCrypto), @@ -307,6 +306,13 @@ ciphers = { 'rc2-cfb': (16, 8, OpenSSLStreamCrypto), 'rc4': (16, 0, OpenSSLStreamCrypto), 'seed-cfb': (16, 16, OpenSSLStreamCrypto), + # AEAD: iv_len = salt_len = key_len + 'aes-128-gcm': (16, 16, OpenSSLAeadCrypto), + 'aes-192-gcm': (24, 24, OpenSSLAeadCrypto), + 'aes-256-gcm': (32, 32, OpenSSLAeadCrypto), + 'aes-128-ocb': (16, 16, OpenSSLAeadCrypto), + 'aes-192-ocb': (24, 24, OpenSSLAeadCrypto), + 'aes-256-ocb': (32, 32, OpenSSLAeadCrypto), } @@ -353,10 +359,6 @@ def run_aead_method_chunk(method, key_len=16): util.run_cipher(cipher, decipher) -def test_aes_128_cfb(): - run_method('aes-128-cfb') - - def test_aes_gcm(bits=128): method = "aes-{0}-gcm".format(bits) run_aead_method(method, bits / 8) @@ -377,6 +379,10 @@ def test_aes_ocb_chunk(bits=128): run_aead_method_chunk(method, bits / 8) +def test_aes_128_cfb(): + run_method('aes-128-cfb') + + def test_aes_256_cfb(): run_method('aes-256-cfb') @@ -404,6 +410,7 @@ def test_rc4(): if __name__ == '__main__': test_aes_128_cfb() test_aes_256_cfb() + test_aes_256_ofb() test_aes_gcm(128) test_aes_gcm(192) test_aes_gcm(256) diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 2f7f22e..014fa3c 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -23,13 +23,14 @@ from shadowsocks.crypto import openssl __all__ = ['ciphers'] -def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, +def create_cipher(alg, key, iv, op, crypto_path=None, + key_as_bytes=0, d=None, salt=None, i=1, padding=1): md5 = hashlib.md5() md5.update(key) md5.update(iv) rc4_key = md5.digest() - return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op) + return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op, crypto_path) ciphers = { diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index 69196a3..c5a6c5c 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -17,7 +17,7 @@ from __future__ import absolute_import, division, print_function, \ with_statement -from ctypes import c_char_p, c_int, c_ulonglong, byref, c_ulong, \ +from ctypes import c_char_p, c_int, c_uint, c_ulonglong, byref, \ create_string_buffer, c_void_p from shadowsocks.crypto import util @@ -36,18 +36,21 @@ buf_size = 2048 BLOCK_SIZE = 64 -def load_libsodium(): +def load_libsodium(crypto_path=None): global loaded, libsodium, buf + crypto_path = dict(crypto_path) if crypto_path else dict() + path = crypto_path.get('sodium', None) + if not aead.sodium_loaded: - aead.load_sodium() + aead.load_sodium(path) if aead.sodium_loaded: libsodium = aead.libsodium else: - print('load libsodium again') + print('load libsodium again with path %s' % path) libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', - 'libsodium') + 'libsodium', path) if libsodium is None: raise Exception('libsodium not found') @@ -55,39 +58,52 @@ def load_libsodium(): raise Exception('libsodium init failed') libsodium.crypto_stream_salsa20_xor_ic.restype = c_int - libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, - c_ulonglong, - c_char_p, c_ulonglong, - c_char_p) + libsodium.crypto_stream_salsa20_xor_ic.argtypes = ( + c_void_p, c_char_p, # cipher output, msg + c_ulonglong, # msg len + c_char_p, c_ulonglong, # nonce, uint64_t initial block counter + c_char_p # key + ) libsodium.crypto_stream_chacha20_xor_ic.restype = c_int - libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, - c_ulonglong, - c_char_p, c_ulonglong, - c_char_p) + libsodium.crypto_stream_chacha20_xor_ic.argtypes = ( + c_void_p, c_char_p, + c_ulonglong, + c_char_p, c_ulonglong, + c_char_p + ) + if hasattr(libsodium, 'crypto_stream_xchacha20_xor_ic'): + libsodium.crypto_stream_xchacha20_xor_ic.restype = c_int + libsodium.crypto_stream_xchacha20_xor_ic.argtypes = ( + c_void_p, c_char_p, + c_ulonglong, + c_char_p, c_ulonglong, + c_char_p + ) libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int - libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, - c_char_p, - c_ulonglong, - c_char_p, - c_ulong, - c_char_p) + libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = ( + c_void_p, c_char_p, + c_ulonglong, + c_char_p, + c_uint, # uint32_t initial counter + c_char_p + ) # chacha20-poly1305 libsodium.crypto_aead_chacha20poly1305_encrypt.restype = c_int libsodium.crypto_aead_chacha20poly1305_encrypt.argtypes = ( - c_void_p, c_void_p, # c, clen + c_void_p, c_void_p, # c, clen c_char_p, c_ulonglong, # m, mlen c_char_p, c_ulonglong, # ad, adlen - c_char_p, # nsec, not used - c_char_p, c_char_p # npub, k + c_char_p, # nsec, not used + c_char_p, c_char_p # npub, k ) libsodium.crypto_aead_chacha20poly1305_decrypt.restype = c_int libsodium.crypto_aead_chacha20poly1305_decrypt.argtypes = ( - c_void_p, c_void_p, # m, mlen - c_char_p, # nsec, not used + c_void_p, c_void_p, # m, mlen + c_char_p, # nsec, not used c_char_p, c_ulonglong, # c, clen c_char_p, c_ulonglong, # ad, adlen - c_char_p, c_char_p # npub, k + c_char_p, c_char_p # npub, k ) # chacha20-ietf-poly1305, same api structure as above @@ -154,9 +170,9 @@ def load_libsodium(): class SodiumCrypto(object): - def __init__(self, cipher_name, key, iv, op): + def __init__(self, cipher_name, key, iv, op, crypto_path=None): if not loaded: - load_libsodium() + load_libsodium(crypto_path) self.key = key self.iv = iv self.key_ptr = c_char_p(key) @@ -165,6 +181,11 @@ class SodiumCrypto(object): self.cipher = libsodium.crypto_stream_salsa20_xor_ic elif cipher_name == 'chacha20': self.cipher = libsodium.crypto_stream_chacha20_xor_ic + elif cipher_name == 'xchacha20': + if hasattr(libsodium, 'crypto_stream_xchacha20_xor_ic'): + self.cipher = libsodium.crypto_stream_xchacha20_xor_ic + else: + raise Exception('Unsupported cipher') elif cipher_name == 'chacha20-ietf': self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic else: @@ -198,32 +219,34 @@ class SodiumCrypto(object): class SodiumAeadCrypto(AeadCryptoBase): - def __init__(self, cipher_name, key, iv, op): + def __init__(self, cipher_name, key, iv, op, crypto_path=None): if not loaded: - load_libsodium() - AeadCryptoBase.__init__(self, cipher_name, key, iv, op) + load_libsodium(crypto_path) + AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path) if cipher_name == 'chacha20-poly1305': self.encryptor = libsodium.crypto_aead_chacha20poly1305_encrypt self.decryptor = libsodium.crypto_aead_chacha20poly1305_decrypt elif cipher_name == 'chacha20-ietf-poly1305': - self.encryptor = libsodium.\ + self.encryptor = libsodium. \ crypto_aead_chacha20poly1305_ietf_encrypt - self.decryptor = libsodium.\ + self.decryptor = libsodium. \ crypto_aead_chacha20poly1305_ietf_decrypt elif cipher_name == 'xchacha20-ietf-poly1305': if hasattr(libsodium, 'crypto_aead_xchacha20poly1305_ietf_encrypt'): - self.encryptor = libsodium.\ + self.encryptor = libsodium. \ crypto_aead_xchacha20poly1305_ietf_encrypt - self.decryptor = libsodium.\ + self.decryptor = libsodium. \ crypto_aead_xchacha20poly1305_ietf_decrypt else: - raise Exception('Unknown cipher') + raise Exception('Unsupported cipher') elif cipher_name == 'sodium:aes-256-gcm': if hasattr(libsodium, 'crypto_aead_aes256gcm_encrypt'): self.encryptor = libsodium.crypto_aead_aes256gcm_encrypt self.decryptor = libsodium.crypto_aead_aes256gcm_decrypt + else: + raise Exception('Unsupported cipher') else: raise Exception('Unknown cipher') @@ -269,7 +292,7 @@ class SodiumAeadCrypto(AeadCryptoBase): raise Exception("Decrypt failed") if cipher_out_len.value != clen - self._tlen: - raise Exception("Encrypt failed") + raise Exception("Decrypt failed") self.cipher_ctx_init() return buf.raw[:cipher_out_len.value] @@ -278,7 +301,9 @@ class SodiumAeadCrypto(AeadCryptoBase): ciphers = { 'salsa20': (32, 8, SodiumCrypto), 'chacha20': (32, 8, SodiumCrypto), + 'xchacha20': (32, 24, SodiumCrypto), 'chacha20-ietf': (32, 12, SodiumCrypto), + # AEAD: iv_len = salt_len = key_len 'chacha20-poly1305': (32, 32, SodiumAeadCrypto), 'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), 'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), @@ -286,17 +311,7 @@ ciphers = { } -def test_salsa20(): - - print("Test salsa20") - 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(): - print("Test chacha20") cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) @@ -304,8 +319,23 @@ def test_chacha20(): util.run_cipher(cipher, decipher) -def test_chacha20_ietf(): +def test_xchacha20(): + print("Test xchacha20") + cipher = SodiumCrypto('xchacha20', b'k' * 32, b'i' * 24, 1) + decipher = SodiumCrypto('xchacha20', b'k' * 32, b'i' * 24, 0) + util.run_cipher(cipher, decipher) + + +def test_salsa20(): + print("Test salsa20") + 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_ietf(): print("Test chacha20-ietf") cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) @@ -314,7 +344,6 @@ def test_chacha20_ietf(): def test_chacha20_poly1305(): - print("Test chacha20-poly1305 [payload][tag]") cipher = SodiumAeadCrypto('chacha20-poly1305', b'k' * 32, b'i' * 32, 1) @@ -325,7 +354,6 @@ def test_chacha20_poly1305(): def test_chacha20_poly1305_chunk(): - print("Test chacha20-poly1305 chunk [size][tag][payload][tag]") cipher = SodiumAeadCrypto('chacha20-poly1305', b'k' * 32, b'i' * 32, 1) @@ -339,7 +367,6 @@ def test_chacha20_poly1305_chunk(): def test_chacha20_ietf_poly1305(): - print("Test chacha20-ietf-poly1305 [payload][tag]") cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', b'k' * 32, b'i' * 32, 1) @@ -350,7 +377,6 @@ def test_chacha20_ietf_poly1305(): def test_chacha20_ietf_poly1305_chunk(): - print("Test chacha20-ietf-poly1305 chunk [size][tag][payload][tag]") cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', b'k' * 32, b'i' * 32, 1) @@ -364,7 +390,6 @@ def test_chacha20_ietf_poly1305_chunk(): def test_aes_256_gcm(): - print("Test sodium:aes-256-gcm [payload][tag]") cipher = SodiumAeadCrypto('sodium:aes-256-gcm', b'k' * 32, b'i' * 32, 1) @@ -375,7 +400,6 @@ def test_aes_256_gcm(): def test_aes_256_gcm_chunk(): - print("Test sodium:aes-256-gcm chunk [size][tag][payload][tag]") cipher = SodiumAeadCrypto('sodium:aes-256-gcm', b'k' * 32, b'i' * 32, 1) @@ -390,6 +414,7 @@ def test_aes_256_gcm_chunk(): if __name__ == '__main__': test_chacha20() + test_xchacha20() test_salsa20() test_chacha20_ietf() test_chacha20_poly1305() diff --git a/shadowsocks/crypto/table.py b/shadowsocks/crypto/table.py index 9e11002..1752be5 100644 --- a/shadowsocks/crypto/table.py +++ b/shadowsocks/crypto/table.py @@ -55,7 +55,7 @@ def init_table(key): class TableCipher(object): - def __init__(self, cipher_name, key, iv, op): + def __init__(self, cipher_name, key, iv, op, crypto_path=None): self._encrypt_table, self._decrypt_table = init_table(key) self._op = op self.encrypt = self.update diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index cf20e24..f52e7ba 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -41,9 +41,27 @@ def find_library_nt(name): return results -def find_library(possible_lib_names, search_symbol, library_name): - import ctypes.util +def load_library(path, search_symbol, library_name): from ctypes import CDLL + try: + lib = CDLL(path) + if hasattr(lib, search_symbol): + logging.info('loading %s from %s', library_name, path) + return lib + else: + logging.warn('can\'t find symbol %s in %s', search_symbol, + path) + except Exception: + pass + return None + + +def find_library(possible_lib_names, search_symbol, library_name, + custom_path=None): + import ctypes.util + + if custom_path: + return load_library(custom_path, search_symbol, library_name) paths = [] @@ -81,16 +99,9 @@ def find_library(possible_lib_names, search_symbol, library_name): if files: paths.extend(files) for path in paths: - try: - lib = CDLL(path) - if hasattr(lib, search_symbol): - logging.info('loading %s from %s', library_name, path) - return lib - else: - logging.warn('can\'t find symbol %s in %s', search_symbol, - path) - except Exception: - pass + lib = load_library(path, search_symbol, library_name) + if lib: + return lib return None diff --git a/shadowsocks/cryptor.py b/shadowsocks/cryptor.py index 719b1b6..4eae9e8 100644 --- a/shadowsocks/cryptor.py +++ b/shadowsocks/cryptor.py @@ -23,7 +23,7 @@ import hashlib import logging from shadowsocks import common -from shadowsocks.crypto import rc4_md5, openssl, sodium, table +from shadowsocks.crypto import rc4_md5, openssl, mbedtls, sodium, table CIPHER_ENC_ENCRYPTION = 1 @@ -36,6 +36,7 @@ METHOD_INFO_CRYPTO = 2 method_supported = {} method_supported.update(rc4_md5.ciphers) method_supported.update(openssl.ciphers) +method_supported.update(mbedtls.ciphers) method_supported.update(sodium.ciphers) method_supported.update(table.ciphers) @@ -46,8 +47,8 @@ def random_string(length): cached_keys = {} -def try_cipher(key, method=None): - Cryptor(key, method) +def try_cipher(key, method=None, crypto_path=None): + Cryptor(key, method, crypto_path) def EVP_BytesToKey(password, key_len, iv_len): @@ -75,7 +76,14 @@ def EVP_BytesToKey(password, key_len, iv_len): class Cryptor(object): - def __init__(self, password, method): + def __init__(self, password, method, crypto_path=None): + """ + Crypto wrapper + :param password: str cipher password + :param method: str cipher + :param crypto_path: dict or none + {'openssl': path, 'sodium': path, 'mbedtls': path} + """ self.password = password self.key = None self.method = method @@ -83,6 +91,7 @@ class Cryptor(object): self.cipher_iv = b'' self.decipher = None self.decipher_iv = None + self.crypto_path = crypto_path method = method.lower() self._method_info = Cryptor.get_method_info(method) if self._method_info: @@ -118,7 +127,7 @@ class Cryptor(object): if op == CIPHER_ENC_ENCRYPTION: # this iv is for cipher not decipher self.cipher_iv = iv - return m[METHOD_INFO_CRYPTO](method, key, iv, op) + return m[METHOD_INFO_CRYPTO](method, key, iv, op, self.crypto_path) def encrypt(self, buf): if len(buf) == 0: @@ -139,7 +148,7 @@ class Cryptor(object): self.decipher = self.get_cipher( self.password, self.method, CIPHER_ENC_DECRYPTION, - iv=decipher_iv + decipher_iv ) buf = buf[decipher_iv_len:] if len(buf) == 0: @@ -158,30 +167,30 @@ def gen_key_iv(password, method): return key, iv, m -def encrypt_all_m(key, iv, m, method, data): +def encrypt_all_m(key, iv, m, method, data, crypto_path=None): result = [iv] - cipher = m(method, key, iv, 1) + cipher = m(method, key, iv, 1, crypto_path) result.append(cipher.encrypt_once(data)) return b''.join(result) -def decrypt_all(password, method, data): +def decrypt_all(password, method, data, crypto_path=None): result = [] method = method.lower() (key, iv, m) = gen_key_iv(password, method) iv = data[:len(iv)] data = data[len(iv):] - cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION) + cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION, crypto_path) result.append(cipher.decrypt_once(data)) return b''.join(result), key, iv -def encrypt_all(password, method, data): +def encrypt_all(password, method, data, crypto_path=None): result = [] method = method.lower() (key, iv, m) = gen_key_iv(password, method) result.append(iv) - cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION) + cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION, crypto_path) result.append(cipher.encrypt_once(data)) return b''.join(result) @@ -189,6 +198,7 @@ def encrypt_all(password, method, data): CIPHERS_TO_TEST = [ 'aes-128-cfb', 'aes-256-cfb', + 'aes-256-gcm', 'rc4-md5', 'salsa20', 'chacha20', diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 55aea86..62f187e 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -71,6 +71,7 @@ class Manager(object): port_password = config['port_password'] del config['port_password'] + config['crypto_path'] = config.get('crypto_path', dict()) for port, password in port_password.items(): a_config = config.copy() a_config['server_port'] = int(port) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index af33456..df04cee 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -201,7 +201,12 @@ def check_config(config, is_local): config['dns_server'] = to_str(config['dns_server']) logging.info('Specified DNS server: %s' % config['dns_server']) - cryptor.try_cipher(config['password'], config['method']) + config['crypto_path'] = {'openssl': config['libopenssl'], + 'mbedtls': config['libmbedtls'], + 'sodium': config['libsodium']} + + cryptor.try_cipher(config['password'], config['method'], + config['crypto_path']) def get_config(is_local): @@ -212,12 +217,12 @@ def get_config(is_local): if is_local: shortopts = 'hd:s:b:p:k:l:m:c:t:vqa' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=', - 'version'] + 'libopenssl=', 'libmbedtls=', 'libsodium=', 'version'] else: shortopts = 'hd:s:p:k:m:c:t:vqa' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', 'forbidden-ip=', 'user=', 'manager-address=', 'version', - 'prefer-ipv6'] + 'libopenssl=', 'libmbedtls=', 'libsodium=', 'prefer-ipv6'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -261,10 +266,16 @@ def get_config(is_local): config['timeout'] = int(value) elif key == '--fast-open': config['fast_open'] = True + elif key == '--libopenssl': + config['libopenssl'] = to_str(value) + elif key == '--libmbedtls': + config['libmbedtls'] = to_str(value) + elif key == '--libsodium': + config['libsodium'] = to_str(value) elif key == '--workers': config['workers'] = int(value) elif key == '--manager-address': - config['manager_address'] = value + config['manager_address'] = to_str(value) elif key == '--user': config['user'] = to_str(value) elif key == '--forbidden-ip': @@ -313,11 +324,14 @@ def get_config(is_local): config['one_time_auth'] = config.get('one_time_auth', False) config['prefer_ipv6'] = config.get('prefer_ipv6', False) config['server_port'] = config.get('server_port', 8388) + config['dns_server'] = config.get('dns_server', None) + config['libopenssl'] = config.get('libopenssl', None) + config['libmbedtls'] = config.get('libmbedtls', None) + config['libsodium'] = config.get('libsodium', None) config['tunnel_remote'] = to_str(config.get('tunnel_remote', '8.8.8.8')) config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53) config['tunnel_port'] = config.get('tunnel_port', 53) - config['dns_server'] = config.get('dns_server', None) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') @@ -364,29 +378,38 @@ Proxy options: -m METHOD encryption method, default: aes-256-cfb Sodium: chacha20-poly1305, chacha20-ietf-poly1305, - *xchacha20-ietf-poly1305, + xchacha20-ietf-poly1305, sodium:aes-256-gcm, salsa20, chacha20, chacha20-ietf. - OpenSSL:(* v1.1) - *aes-128-ocb, *aes-192-ocb, *aes-256-ocb, - aes-128-gcm, aes-192-gcm, aes-256-gcm, - aes-128-cfb, aes-192-cfb, aes-256-cfb, - aes-128-ctr, aes-192-ctr, aes-256-ctr, - camellia-128-cfb, camellia-192-cfb, - camellia-256-cfb, + Sodium 1.0.12: + xchacha20 + OpenSSL: + aes-{128|192|256}-gcm, aes-{128|192|256}-cfb, + aes-{128|192|256}-ofb, aes-{128|192|256}-ctr, + camellia-{128|192|256}-cfb, bf-cfb, cast5-cfb, des-cfb, idea-cfb, rc2-cfb, seed-cfb, rc4, rc4-md5, table. + OpenSSL 1.1: + aes-{128|192|256}-ocb + mbedTLS: + mbedtls:aes-{128|192|256}-cfb128, + mbedtls:aes-{128|192|256}-ctr, + mbedtls:camellia-{128|192|256}-cfb128, + mbedtls:aes-{128|192|256}-gcm -t TIMEOUT timeout in seconds, default: 300 -a ONE_TIME_AUTH one time auth --fast-open use TCP_FASTOPEN, requires Linux 3.7+ + --libopenssl=PATH custom openssl crypto lib path + --libmbedtls=PATH custom mbedtls crypto lib path + --libsodium=PATH custom sodium crypto lib path 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 + --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 @@ -409,26 +432,35 @@ Proxy options: -m METHOD encryption method, default: aes-256-cfb Sodium: chacha20-poly1305, chacha20-ietf-poly1305, - *xchacha20-ietf-poly1305, + xchacha20-ietf-poly1305, sodium:aes-256-gcm, salsa20, chacha20, chacha20-ietf. - OpenSSL:(* v1.1) - *aes-128-ocb, *aes-192-ocb, *aes-256-ocb, - aes-128-gcm, aes-192-gcm, aes-256-gcm, - aes-128-cfb, aes-192-cfb, aes-256-cfb, - aes-128-ctr, aes-192-ctr, aes-256-ctr, - camellia-128-cfb, camellia-192-cfb, - camellia-256-cfb, + Sodium 1.0.12: + xchacha20 + OpenSSL: + aes-{128|192|256}-gcm, aes-{128|192|256}-cfb, + aes-{128|192|256}-ofb, aes-{128|192|256}-ctr, + camellia-{128|192|256}-cfb, bf-cfb, cast5-cfb, des-cfb, idea-cfb, rc2-cfb, seed-cfb, rc4, rc4-md5, table. + OpenSSL 1.1: + aes-{128|192|256}-ocb + mbedTLS: + mbedtls:aes-{128|192|256}-cfb128, + mbedtls:aes-{128|192|256}-ctr, + mbedtls:camellia-{128|192|256}-cfb128, + mbedtls:aes-{128|192|256}-gcm -t TIMEOUT timeout in seconds, default: 300 -a ONE_TIME_AUTH one time auth --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-address ADDR optional server manager UDP address, see wiki + --workers=WORKERS number of workers, available on Unix/Linux + --forbidden-ip=IPLIST comma seperated IP list forbidden to connect + --manager-address=ADDR optional server manager UDP address, see wiki --prefer-ipv6 resolve ipv6 address first + --libopenssl=PATH custom openssl crypto lib path + --libmbedtls=PATH custom mbedtls crypto lib path + --libsodium=PATH custom sodium crypto lib path General options: -h, --help show this help message and exit diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index ea85e4d..0ef913f 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -93,6 +93,8 @@ WAIT_STATUS_WRITING = 2 WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING BUF_SIZE = 32 * 1024 +UP_STREAM_BUF_SIZE = 16 * 1024 +DOWN_STREAM_BUF_SIZE = 32 * 1024 # helper exceptions for TCPRelayHandler @@ -126,7 +128,8 @@ class TCPRelayHandler(object): self._is_local = is_local self._stage = STAGE_INIT self._cryptor = cryptor.Cryptor(config['password'], - config['method']) + config['method'], + config['crypto_path']) self._ota_enable = config.get('one_time_auth', False) self._ota_enable_session = self._ota_enable self._ota_buff_head = b'' @@ -553,8 +556,12 @@ class TCPRelayHandler(object): return is_local = self._is_local data = None + if is_local: + buf_size = UP_STREAM_BUF_SIZE + else: + buf_size = DOWN_STREAM_BUF_SIZE try: - data = self._local_sock.recv(BUF_SIZE) + data = self._local_sock.recv(buf_size) except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): @@ -586,8 +593,12 @@ class TCPRelayHandler(object): def _on_remote_read(self): # handle all remote read events data = None + if self._is_local: + buf_size = UP_STREAM_BUF_SIZE + else: + buf_size = DOWN_STREAM_BUF_SIZE try: - data = self._remote_sock.recv(BUF_SIZE) + data = self._remote_sock.recv(buf_size) except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 38af1b6..f726717 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -115,6 +115,8 @@ class UDPRelay(object): self._closed = False self._sockets = set() self._forbidden_iplist = config.get('forbidden_ip') + self._crypto_path = config['crypto_path'] + addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if len(addrs) == 0: @@ -174,7 +176,7 @@ class UDPRelay(object): try: data, key, iv = cryptor.decrypt_all(self._password, self._method, - data) + data, self._crypto_path) except Exception: logging.debug('UDP handle_server: decrypt data failed') return @@ -241,7 +243,8 @@ class UDPRelay(object): if self._ota_enable_session: data = self._ota_chunk_data_gen(key, iv, data) try: - data = cryptor.encrypt_all_m(key, iv, m, self._method, data) + data = cryptor.encrypt_all_m(key, iv, m, self._method, data, + self._crypto_path) except Exception: logging.debug("UDP handle_server: encrypt data failed") return @@ -275,7 +278,8 @@ class UDPRelay(object): data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data try: response = cryptor.encrypt_all(self._password, - self._method, data) + self._method, data, + self._crypto_path) except Exception: logging.debug("UDP handle_client: encrypt data failed") return @@ -284,7 +288,8 @@ class UDPRelay(object): else: try: data, key, iv = cryptor.decrypt_all(self._password, - self._method, data) + self._method, data, + self._crypto_path) except Exception: logging.debug('UDP handle_client: decrypt data failed') return diff --git a/tests/aes-cfb1.json b/tests/aes-cfb1.json index 40d0b21..70ae670 100644 --- a/tests/aes-cfb1.json +++ b/tests/aes-cfb1.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb1", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb1", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-cfb8.json b/tests/aes-cfb8.json index fb7014b..fe8b715 100644 --- a/tests/aes-cfb8.json +++ b/tests/aes-cfb8.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb8", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb8", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-ctr.json b/tests/aes-ctr.json index 1fed8a8..1f5a1d7 100644 --- a/tests/aes-ctr.json +++ b/tests/aes-ctr.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-ctr", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-ctr", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-gcm.json b/tests/aes-gcm.json new file mode 100644 index 0000000..271d721 --- /dev/null +++ b/tests/aes-gcm.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-gcm", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-ocb.json b/tests/aes-ocb.json new file mode 100644 index 0000000..9c1d5b8 --- /dev/null +++ b/tests/aes-ocb.json @@ -0,0 +1,11 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-ocb", + "local_address":"127.0.0.1", + "fast_open":false, + "libopenssl":"/usr/local/lib/libcrypto.so.1.1" +} diff --git a/tests/aes-ofb.json b/tests/aes-ofb.json new file mode 100644 index 0000000..0e2003f --- /dev/null +++ b/tests/aes-ofb.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-ofb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes.json b/tests/aes.json index a3d95b9..2fc29f3 100644 --- a/tests/aes.json +++ b/tests/aes.json @@ -1,10 +1,10 @@ -{ - "server":"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 -} +{ + "server":"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 +} diff --git a/tests/camellia.json b/tests/camellia.json new file mode 100644 index 0000000..d7ffd8c --- /dev/null +++ b/tests/camellia.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"camellia_password", + "timeout":60, + "method":"camellia-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/chacha20-ietf-poly1305.json b/tests/chacha20-ietf-poly1305.json new file mode 100644 index 0000000..074dccb --- /dev/null +++ b/tests/chacha20-ietf-poly1305.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"chacha20-ietf-poly1305", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/chacha20-ietf.json b/tests/chacha20-ietf.json index bfe3e1f..a27987c 100644 --- a/tests/chacha20-ietf.json +++ b/tests/chacha20-ietf.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"chacha20-ietf", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"chacha20-ietf", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/chacha20-poly1305.json b/tests/chacha20-poly1305.json new file mode 100644 index 0000000..d9f2191 --- /dev/null +++ b/tests/chacha20-poly1305.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"chacha20-poly1305", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/chacha20.json b/tests/chacha20.json index 541a9be..6def51b 100644 --- a/tests/chacha20.json +++ b/tests/chacha20.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"chacha20", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"chacha20_password", + "timeout":60, + "method":"chacha20", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/client-multi-server-ip.json b/tests/client-multi-server-ip.json index 1823c2a..3050c11 100644 --- a/tests/client-multi-server-ip.json +++ b/tests/client-multi-server-ip.json @@ -1,10 +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 -} +{ + "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 +} diff --git a/tests/fastopen.json b/tests/fastopen.json index f3980b6..b4f60d4 100644 --- a/tests/fastopen.json +++ b/tests/fastopen.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"fastopen_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":true -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"fastopen_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":true +} diff --git a/tests/graceful.json b/tests/graceful.json index 7d94ea5..c00c5df 100644 --- a/tests/graceful.json +++ b/tests/graceful.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":15, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "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/ipv6-client-side.json b/tests/ipv6-client-side.json index 6c3cfaf..e14bb07 100644 --- a/tests/ipv6-client-side.json +++ b/tests/ipv6-client-side.json @@ -1,10 +1,10 @@ -{ - "server":"::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 -} +{ + "server":"::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 +} diff --git a/tests/ipv6.json b/tests/ipv6.json index d855f9c..18263ee 100644 --- a/tests/ipv6.json +++ b/tests/ipv6.json @@ -1,10 +1,10 @@ -{ - "server":"::", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"::", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 6d0fac8..3a071c8 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -33,12 +33,25 @@ run_test coverage run tests/nose_plugin.py -v run_test python setup.py sdist run_test tests/test_daemon.sh run_test python tests/test.py --with-coverage -c tests/aes.json +run_test python tests/test.py --with-coverage -c tests/mbedtls-aes.json +run_test python tests/test.py --with-coverage -c tests/aes-gcm.json +run_test python tests/test.py --with-coverage -c tests/aes-ocb.json +run_test python tests/test.py --with-coverage -c tests/mbedtls-aes-gcm.json run_test python tests/test.py --with-coverage -c tests/aes-ctr.json +run_test python tests/test.py --with-coverage -c tests/mbedtls-aes-ctr.json run_test python tests/test.py --with-coverage -c tests/aes-cfb1.json run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json +run_test python tests/test.py --with-coverage -c tests/aes-ofb.json +run_test python tests/test.py --with-coverage -c tests/camellia.json +run_test python tests/test.py --with-coverage -c tests/mbedtls-camellia.json run_test python tests/test.py --with-coverage -c tests/rc4-md5.json 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/xchacha20.json +run_test python tests/test.py --with-coverage -c tests/chacha20-ietf.json +run_test python tests/test.py --with-coverage -c tests/chacha20-poly1305.json +run_test python tests/test.py --with-coverage -c tests/xchacha20-ietf-poly1305.json +run_test python tests/test.py --with-coverage -c tests/chacha20-ietf-poly1305.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 @@ -52,6 +65,15 @@ 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 custom lib path + +run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libopenssl=/usr/local/lib/libcrypto.so" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libopenssl=/usr/local/lib/libcrypto.so" +run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m mbedtls:aes-256-cfb128 -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libmbedtls=/usr/local/lib/libmbedcrypto.so" -a "-m mbedtls:aes-256-cfb128 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libmbedtls=/usr/local/lib/libmbedcrypto.so" +run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libsodium=/usr/local/lib/libsodium.so" -a "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libsodium=/usr/local/lib/libsodium.so" +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= --libopenssl=invalid_path" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libopenssl=invalid_path" +run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libsodium=invalid_path" -a "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libsodium=invalid_path" +run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m mbedtls:aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libmbedtls=invalid_path" -a "-m mbedtls:aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libmbedtls=invalid_path" + # test if DNS works run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://clients1.google.com/generate_204" diff --git a/tests/libmbedtls/install.sh b/tests/libmbedtls/install.sh new file mode 100755 index 0000000..59fb512 --- /dev/null +++ b/tests/libmbedtls/install.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +MBEDTLS_VER=2.4.2 +if [ ! -d mbedtls-$MBEDTLS_VER ]; then + wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz || exit 1 + tar xf mbedtls-$MBEDTLS_VER-gpl.tgz || exit 1 +fi +pushd mbedtls-$MBEDTLS_VER +make SHARED=1 CFLAGS=-fPIC && sudo make install || exit 1 +sudo ldconfig +popd +rm -rf mbedtls-$MBEDTLS_VER || exit 1 diff --git a/tests/libopenssl/install.sh b/tests/libopenssl/install.sh new file mode 100755 index 0000000..e948bff --- /dev/null +++ b/tests/libopenssl/install.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +OPENSSL_VER=1.1.0e +if [ ! -d openssl-$OPENSSL_VER ]; then + wget https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || exit 1 + tar xf openssl-$OPENSSL_VER.tar.gz || exit 1 +fi +pushd openssl-$OPENSSL_VER +./config && make && sudo make install || exit 1 +# sudo ldconfig # test multiple libcrypto +popd +rm -rf openssl-$OPENSSL_VER || exit 1 diff --git a/tests/libsodium/install.sh b/tests/libsodium/install.sh index 790e471..215ef72 100755 --- a/tests/libsodium/install.sh +++ b/tests/libsodium/install.sh @@ -1,10 +1,11 @@ #!/bin/bash -if [ ! -d libsodium-1.0.11 ]; then - wget https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz || exit 1 - tar xf libsodium-1.0.11.tar.gz || exit 1 +if [ ! -d libsodium-1.0.12 ]; then + wget https://github.com/jedisct1/libsodium/releases/download/1.0.12/libsodium-1.0.12.tar.gz || exit 1 + tar xf libsodium-1.0.12.tar.gz || exit 1 fi -pushd libsodium-1.0.11 +pushd libsodium-1.0.12 ./configure && make -j2 && make install || exit 1 sudo ldconfig popd +rm -rf libsodium-1.0.12 || exit 1 diff --git a/tests/mbedtls-aes-ctr.json b/tests/mbedtls-aes-ctr.json new file mode 100644 index 0000000..0740eea --- /dev/null +++ b/tests/mbedtls-aes-ctr.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"mbedtls:aes-256-ctr", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/mbedtls-aes-gcm.json b/tests/mbedtls-aes-gcm.json new file mode 100644 index 0000000..c504e1c --- /dev/null +++ b/tests/mbedtls-aes-gcm.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"mbedtls:aes-256-gcm", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/mbedtls-aes.json b/tests/mbedtls-aes.json new file mode 100644 index 0000000..c0b097b --- /dev/null +++ b/tests/mbedtls-aes.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"mbedtls:aes-256-cfb128", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/mbedtls-camellia.json b/tests/mbedtls-camellia.json new file mode 100644 index 0000000..480e2ed --- /dev/null +++ b/tests/mbedtls-camellia.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"camellia_password", + "timeout":60, + "method":"mbedtls:camellia-256-cfb128", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/rc4-md5-ota.json b/tests/rc4-md5-ota.json index 3566d6d..6c312c7 100644 --- a/tests/rc4-md5-ota.json +++ b/tests/rc4-md5-ota.json @@ -1,11 +1,11 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"rc4-md5", - "local_address":"127.0.0.1", - "fast_open":false, - "one_time_auth":true -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"rc4-md5", + "local_address":"127.0.0.1", + "fast_open":false, + "one_time_auth":true +} diff --git a/tests/rc4-md5.json b/tests/rc4-md5.json index 26ba0df..e69b433 100644 --- a/tests/rc4-md5.json +++ b/tests/rc4-md5.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"rc4-md5", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"rc4-md5", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/salsa20-ctr.json b/tests/salsa20-ctr.json index 5ca6c45..8b77d07 100644 --- a/tests/salsa20-ctr.json +++ b/tests/salsa20-ctr.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"salsa20-ctr", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"salsa20-ctr", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/salsa20.json b/tests/salsa20.json index 7e30380..a4a664f 100644 --- a/tests/salsa20.json +++ b/tests/salsa20.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"salsa20", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"salsa20", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/server-dnsserver.json b/tests/server-dnsserver.json index 5d55cdc..f8d5902 100644 --- a/tests/server-dnsserver.json +++ b/tests/server-dnsserver.json @@ -1,11 +1,11 @@ -{ - "server":"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, - "dns_server": ["8.8.8.8","8.8.4.4"] -} +{ + "server":"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, + "dns_server": ["8.8.8.8","8.8.4.4"] +} diff --git a/tests/table.json b/tests/table.json index cca6ac2..49c2c01 100644 --- a/tests/table.json +++ b/tests/table.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"table_password", - "timeout":60, - "method":"table", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"table_password", + "timeout":60, + "method":"table", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/workers.json b/tests/workers.json index 2015ff6..8e0943b 100644 --- a/tests/workers.json +++ b/tests/workers.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"workers_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "workers": 4 -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"workers_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "workers": 4 +} diff --git a/tests/xchacha20-ietf-poly1305.json b/tests/xchacha20-ietf-poly1305.json new file mode 100644 index 0000000..91d27dd --- /dev/null +++ b/tests/xchacha20-ietf-poly1305.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"xchacha20-ietf-poly1305", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/xchacha20.json b/tests/xchacha20.json new file mode 100644 index 0000000..bf6f365 --- /dev/null +++ b/tests/xchacha20.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"xchacha20_password", + "timeout":60, + "method":"xchacha20", + "local_address":"127.0.0.1", + "fast_open":false +} From 06b028b5c08c80931482327eccfb6abf1b2ff15c Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Sat, 29 Apr 2017 14:29:53 +0800 Subject: [PATCH 184/196] Fix memory leak when excpetions occur (#814) --- shadowsocks/crypto/aead.py | 4 ++++ shadowsocks/crypto/openssl.py | 5 +++++ shadowsocks/crypto/sodium.py | 3 +++ 3 files changed, 12 insertions(+) diff --git a/shadowsocks/crypto/aead.py b/shadowsocks/crypto/aead.py index d800f36..7c52843 100644 --- a/shadowsocks/crypto/aead.py +++ b/shadowsocks/crypto/aead.py @@ -206,10 +206,12 @@ class AeadCryptoBase(object): # network byte order ctext = [self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))] if len(ctext[0]) != AEAD_CHUNK_SIZE_LEN + self._tlen: + self.clean() raise Exception("size length invalid") ctext.append(self.aead_encrypt(data)) if len(ctext[1]) != plen + self._tlen: + self.clean() raise Exception("data length invalid") return b''.join(ctext) @@ -265,6 +267,7 @@ class AeadCryptoBase(object): plen = self.aead_decrypt(data[:hlen]) plen, = unpack("!H", plen) if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0: + self.clean() raise Exception('Invalid message length') return plen, data[hlen:] @@ -288,6 +291,7 @@ class AeadCryptoBase(object): plaintext = self.aead_decrypt(data[:plen + self._tlen]) if len(plaintext) != plen: + self.clean() raise Exception("plaintext length invalid") return plaintext, data[plen + self._tlen:] diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index 7f7eef2..f2948ae 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -165,6 +165,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): None ) if not r: + self.clean() raise Exception('Set ivlen failed') self.cipher_ctx_init() @@ -201,6 +202,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): c_int(tag_len), c_char_p(tag) ) if not r: + self.clean() raise Exception('Set tag failed') def get_tag(self): @@ -216,6 +218,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): c_int(tag_len), byref(tag_buf) ) if not r: + self.clean() raise Exception('Get tag failed') return tag_buf.raw[:tag_len] @@ -231,6 +234,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): byref(buf), byref(cipher_out_len) ) if not r: + self.clean() # print(self._nonce.raw, r, cipher_out_len) raise Exception('Finalize cipher failed') return buf.raw[:cipher_out_len.value] @@ -255,6 +259,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): """ clen = len(data) if clen < self._tlen: + self.clean() raise Exception('Data too short') self.set_tag(data[clen - self._tlen:]) diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index c5a6c5c..fce65c0 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -217,6 +217,9 @@ class SodiumCrypto(object): # strip off the padding return buf.raw[padding:padding + l] + def clean(self): + pass + class SodiumAeadCrypto(AeadCryptoBase): def __init__(self, cipher_name, key, iv, op, crypto_path=None): From 13b5d6e5ecebef7ee88a48e4f4314dc17dcb7b2d Mon Sep 17 00:00:00 2001 From: Zou Yong Date: Fri, 12 May 2017 03:06:38 -0500 Subject: [PATCH 185/196] Fix udp dns issue under python3 (#849) str is not bytes under python3, if dns config is a list, will cause asyncdns.py failed: asyncdns.py: .......... def handle_event(self, sock, fd, event): if sock != self._sock: return if event & eventloop.POLL_ERR: ........... 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) --- shadowsocks/shell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index df04cee..d508049 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -199,6 +199,8 @@ def check_config(config, is_local): if config.get('dns_server', None) is not None: if type(config['dns_server']) != list: config['dns_server'] = to_str(config['dns_server']) + else: + config['dns_server'] = [to_str(ds) for ds in config['dns_server']] logging.info('Specified DNS server: %s' % config['dns_server']) config['crypto_path'] = {'openssl': config['libopenssl'], From d5026cf5ef05ef5daadd2ea1fec9f50e7347e856 Mon Sep 17 00:00:00 2001 From: Ted George Date: Fri, 12 May 2017 09:07:36 +0100 Subject: [PATCH 186/196] Update README.md (#841) thanks --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 346e53e..4631576 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ shadowsocks [![PyPI version]][PyPI] [![Build Status]][Travis CI] -[![Coverage Status]][Coverage] A fast tunnel proxy that helps you bypass firewalls. @@ -78,8 +77,6 @@ Apache License [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat -[Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks -[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html [PyPI]: https://pypi.python.org/pypi/shadowsocks [PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat [Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks From 893f9099b34171030c36e6b1612207e77f5ff021 Mon Sep 17 00:00:00 2001 From: Esdeath Date: Wed, 6 Sep 2017 15:07:19 +0800 Subject: [PATCH 187/196] Fix memory leak (#921) * Fix memory leak * Fix memory leak(2) * refine --- shadowsocks/crypto/aead.py | 3 --- shadowsocks/crypto/openssl.py | 22 ++++++++++++++++++---- shadowsocks/crypto/sodium.py | 22 ++++++++++++++++++---- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/shadowsocks/crypto/aead.py b/shadowsocks/crypto/aead.py index 7c52843..c7240b3 100644 --- a/shadowsocks/crypto/aead.py +++ b/shadowsocks/crypto/aead.py @@ -155,9 +155,6 @@ class AeadCryptoBase(object): # n, n > 0, waiting data self._chunk = {'mlen': AEAD_MSG_LEN_UNKNOWN, 'data': b''} - self.encrypt_once = self.aead_encrypt - self.decrypt_once = self.aead_decrypt - # load libsodium for nonce increment if not sodium_loaded: crypto_path = dict(crypto_path) if crypto_path else dict() diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index f2948ae..f034bd1 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -107,8 +107,11 @@ class OpenSSLCryptoBase(object): if not self._ctx: raise Exception('can not create cipher context') - self.encrypt_once = self.update - self.decrypt_once = self.update + def encrypt_once(self, data): + return self.update(data) + + def decrypt_once(self, data): + return self.update(data) def update(self, data): """ @@ -136,6 +139,7 @@ class OpenSSLCryptoBase(object): if self._ctx: ctx_cleanup(self._ctx) libcrypto.EVP_CIPHER_CTX_free(self._ctx) + self._ctx = None class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): @@ -267,6 +271,12 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): self.cipher_ctx_init() return plaintext + def encrypt_once(self, data): + return self.aead_encrypt(data) + + def decrypt_once(self, data): + return self.aead_decrypt(data) + class OpenSSLStreamCrypto(OpenSSLCryptoBase): """ @@ -281,8 +291,12 @@ class OpenSSLStreamCrypto(OpenSSLCryptoBase): if not r: self.clean() raise Exception('can not initialize cipher context') - self.encrypt = self.update - self.decrypt = self.update + + def encrypt(self, data): + return self.update(data) + + def decrypt(self, data): + return self.update(data) ciphers = { diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index fce65c0..981321e 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -192,10 +192,18 @@ class SodiumCrypto(object): raise Exception('Unknown cipher') # byte counter, not block counter self.counter = 0 - self.encrypt = self.update - self.decrypt = self.update - self.encrypt_once = self.update - self.decrypt_once = self.update + + def encrypt(self, data): + return self.update(data) + + def decrypt(self, data): + return self.update(data) + + def encrypt_once(self, data): + return self.update(data) + + def decrypt_once(self, data): + return self.update(data) def update(self, data): global buf_size, buf @@ -300,6 +308,12 @@ class SodiumAeadCrypto(AeadCryptoBase): self.cipher_ctx_init() return buf.raw[:cipher_out_len.value] + def encrypt_once(self, data): + return self.aead_encrypt(data) + + def decrypt_once(self, data): + return self.aead_decrypt(data) + ciphers = { 'salsa20': (32, 8, SodiumCrypto), From 3c82f0234356ed3a410886e2c81e3be37e47ff0e Mon Sep 17 00:00:00 2001 From: Minwei Shen Date: Wed, 6 Sep 2017 00:12:37 -0700 Subject: [PATCH 188/196] Improve lru_cache. Update test for it (#897) --- shadowsocks/lru_cache.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/shadowsocks/lru_cache.py b/shadowsocks/lru_cache.py index ff4fc7d..55cb346 100644 --- a/shadowsocks/lru_cache.py +++ b/shadowsocks/lru_cache.py @@ -79,18 +79,15 @@ class LRUCache(collections.MutableMapping): least = self._last_visits[0] if now - least <= self.timeout: break - if self.close_callback is not None: - for key in self._time_to_keys[least]: - if key in self._store: - if now - self._keys_to_last_time[key] > self.timeout: - value = self._store[key] - if value not in self._closed_values: - self.close_callback(value) - self._closed_values.add(value) self._last_visits.popleft() for key in self._time_to_keys[least]: if key in self._store: if now - self._keys_to_last_time[key] > self.timeout: + if self.close_callback is not None: + value = self._store[key] + if value not in self._closed_values: + self.close_callback(value) + self._closed_values.add(value) del self._store[key] del self._keys_to_last_time[key] c += 1 @@ -140,6 +137,7 @@ def test(): c = LRUCache(timeout=0.1, close_callback=close_cb) c['s'] = 1 + c['t'] = 1 c['s'] time.sleep(0.1) c['s'] From 91e6237794e13d7ce734d0586d89924a68da2ada Mon Sep 17 00:00:00 2001 From: Anthony Wong Date: Wed, 6 Sep 2017 15:13:26 +0800 Subject: [PATCH 189/196] Update README.md (#734) Add installation and usage instructions for snap package. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 4631576..ade2523 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ CentOS: yum install python-setuptools && easy_install pip pip install git+https://github.com/shadowsocks/shadowsocks.git@master +Linux distributions with [snap](http://snapcraft.io/): + + snap install shadowsocks + Windows: See [Install Shadowsocks Server on Windows](https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows). @@ -51,6 +55,11 @@ To check the log: Check all the options via `-h`. You can also use a [Configuration] file instead. +If you installed the [snap](http://snapcraft.io/) package, you have to prefix the commands with `shadowsocks.`, +like this: + + shadowsocks.ssserver -p 443 -k password -m aes-256-cfb + ### Usage with Config File [Create configeration file and run](https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File) From 0b0379b8308e2b8510bdb6bcb6f9144a15836bce Mon Sep 17 00:00:00 2001 From: Anthony Wong Date: Wed, 6 Sep 2017 15:13:39 +0800 Subject: [PATCH 190/196] Add new file snapcraft.yaml for building snap package (#932) --- snapcraft.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 snapcraft.yaml diff --git a/snapcraft.yaml b/snapcraft.yaml new file mode 100644 index 0000000..6860270 --- /dev/null +++ b/snapcraft.yaml @@ -0,0 +1,23 @@ +name: shadowsocks +version: 2.9.1-1 +summary: A fast tunnel proxy that helps you bypass firewalls +description: A fast tunnel proxy that helps you bypass firewalls +confinement: strict +grade: stable + +apps: + sslocal: + command: bin/sslocal + plugs: [network, network-bind] + aliases: [sslocal] + + ssserver: + command: bin/ssserver + plugs: [network, network-bind] + aliases: [ssserver] + +parts: + shadowsocks: + plugin: python + python-version: python2 + source: https://github.com/shadowsocks/shadowsocks/archive/2.9.1.tar.gz From 0168be7c92940fc1b2f69436e5d2787fb8b77bb3 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Wed, 6 Sep 2017 15:19:45 +0800 Subject: [PATCH 191/196] Update .travis.yml new path for nginx static file --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dda0661..f11e179 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ cache: 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 dd if=/dev/urandom of=/usr/share/nginx/html/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 PySocks From f7d7aee3a6f77801a93ff9d384fd1b4664bd7749 Mon Sep 17 00:00:00 2001 From: Andrew Lyu Date: Wed, 6 Sep 2017 15:21:13 +0800 Subject: [PATCH 192/196] Fix typo (#866) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ade2523..99b669e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ like this: ### Usage with Config File -[Create configeration file and run](https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File) +[Create configuration file and run](https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File) To start: From 2ab8c6bf5de7e7a5a2011a78d977ca23cbc7b0b3 Mon Sep 17 00:00:00 2001 From: mengskysama Date: Wed, 6 Sep 2017 16:11:14 +0800 Subject: [PATCH 193/196] Update jenkins.sh rm CI coverage --- tests/jenkins.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 3a071c8..85dd01a 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -102,11 +102,10 @@ fi run_test tests/test_udp_src.sh run_test tests/test_command.sh -coverage combine && coverage report --include=shadowsocks/* -rm -rf htmlcov -rm -rf tmp -coverage html --include=shadowsocks/* - -coverage report --include=shadowsocks/* | tail -n1 | rev | cut -d' ' -f 1 | rev > /tmp/shadowsocks-coverage +# coverage combine && coverage report --include=shadowsocks/* +# rm -rf htmlcov +# rm -rf tmp +# coverage html --include=shadowsocks/* +# coverage report --include=shadowsocks/* | tail -n1 | rev | cut -d' ' -f 1 | rev > /tmp/shadowsocks-coverage exit $result From 8b302646eda27854749b5d7b2e53ff64e8c4199e Mon Sep 17 00:00:00 2001 From: mengskysama Date: Mon, 19 Feb 2018 01:08:52 +0800 Subject: [PATCH 194/196] update run_method() (#1161) --- shadowsocks/crypto/mbedtls.py | 9 +++------ shadowsocks/crypto/openssl.py | 4 ++++ tests/libopenssl/install.sh | 7 +++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/shadowsocks/crypto/mbedtls.py b/shadowsocks/crypto/mbedtls.py index b87c2eb..1954a86 100644 --- a/shadowsocks/crypto/mbedtls.py +++ b/shadowsocks/crypto/mbedtls.py @@ -408,22 +408,20 @@ ciphers = { def run_method(method): - from shadowsocks.crypto import openssl print(method, ': [stream]', 32) cipher = MbedTLSStreamCrypto(method, b'k' * 32, b'i' * 16, 1) - decipher = openssl.OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0) + decipher = MbedTLSStreamCrypto(method, b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) def run_aead_method(method, key_len=16): - from shadowsocks.crypto import openssl print(method, ': [payload][tag]', key_len) key_len = int(key_len) cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) - decipher = openssl.OpenSSLAeadCrypto( + decipher = MbedTLSAeadCrypto( method, b'k' * key_len, b'i' * key_len, 0 ) @@ -432,12 +430,11 @@ def run_aead_method(method, key_len=16): def run_aead_method_chunk(method, key_len=16): - from shadowsocks.crypto import openssl print(method, ': chunk([size][tag][payload][tag]', key_len) key_len = int(key_len) cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) - decipher = openssl.OpenSSLAeadCrypto( + decipher = MbedTLSAeadCrypto( method, b'k' * key_len, b'i' * key_len, 0 ) diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index f034bd1..ff63541 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -346,6 +346,8 @@ def run_method(method): def run_aead_method(method, key_len=16): + if not loaded: + load_openssl(None) print(method, ': [payload][tag]', key_len) cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method)) if not cipher: @@ -362,6 +364,8 @@ def run_aead_method(method, key_len=16): def run_aead_method_chunk(method, key_len=16): + if not loaded: + load_openssl(None) print(method, ': chunk([size][tag][payload][tag]', key_len) cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method)) if not cipher: diff --git a/tests/libopenssl/install.sh b/tests/libopenssl/install.sh index e948bff..480c772 100755 --- a/tests/libopenssl/install.sh +++ b/tests/libopenssl/install.sh @@ -10,3 +10,10 @@ pushd openssl-$OPENSSL_VER # sudo ldconfig # test multiple libcrypto popd rm -rf openssl-$OPENSSL_VER || exit 1 + +rm /usr/bin/openssl || exit 1 +rm -r /usr/include/openssl || exit 1 +ln -s /usr/local/bin/openssl /usr/bin/openssl || exit 1 +ln -s /usr/local/include/openssl /usr/include/openssl || exit 1 +echo /usr/local/lib >> /etc/ld.so.conf || exit 1 +ldconfig -v || exit 1 From c668f44c6839c82155ee3d6924adebf350d57219 Mon Sep 17 00:00:00 2001 From: netroby Date: Mon, 19 Feb 2018 01:22:57 +0800 Subject: [PATCH 195/196] Update README.md (#1042) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 99b669e..d87232a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,11 @@ CentOS: yum install python-setuptools && easy_install pip pip install git+https://github.com/shadowsocks/shadowsocks.git@master +For CentOS 7, if you need AEAD ciphers, you need install libsodium +``` +dnf install libsodium python34-pip +pip3 install git+https://github.com/shadowsocks/shadowsocks.git@master +``` Linux distributions with [snap](http://snapcraft.io/): snap install shadowsocks From e332ec93e9c90f1cbee676b022bf2c5d5b7b1239 Mon Sep 17 00:00:00 2001 From: "Shell.Xu" Date: Mon, 19 Feb 2018 10:42:32 +0800 Subject: [PATCH 196/196] use list instead of string, prevent injection attack. (#1009) * fix issue: https://github.com/shadowsocks/shadowsocks/issues/995 Command Execution use list instead of string, prevent injection attack. --- utils/autoban.py | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/utils/autoban.py b/utils/autoban.py index c7af0a5..52aa163 100755 --- a/utils/autoban.py +++ b/utils/autoban.py @@ -24,9 +24,17 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import os import sys +import socket import argparse +import subprocess + + +def inet_pton(str_ip): + try: + return socket.inet_pton(socket.AF_INET, str_ip) + except socket.error: + return None if __name__ == '__main__': parser = argparse.ArgumentParser(description='See README') @@ -37,17 +45,22 @@ if __name__ == '__main__': ips = {} banned = set() for line in sys.stdin: - if 'can not parse header when' in line: - ip = line.split()[-1].split(':')[-2] - 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) + if 'can not parse header when' not in line: + continue + ip_str = line.split()[-1].rsplit(':', 1)[0] + ip = inet_pton(ip_str) + if ip is None: + continue + if ip not in ips: + ips[ip] = 1 + sys.stdout.flush() + else: + ips[ip] += 1 + if ip not in banned and ips[ip] >= config.count: + banned.add(ip) + print('ban ip %s' % ip_str) + cmd = ['iptables', '-A', 'INPUT', '-s', ip_str, '-j', 'DROP', + '-m', 'comment', '--comment', 'autoban'] + print(' '.join(cmd), file=sys.stderr) + sys.stderr.flush() + subprocess.call(cmd)