From a2bc6e19457f51a421b7d2866be5903e0d71fd2f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 11 Aug 2015 17:55:16 +0800 Subject: [PATCH 01/32] 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 02/32] 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 03/32] 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 04/32] 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 05/32] 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 06/32] 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 07/32] 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 767b9217f874db37e947f5883f06e11e64ba9861 Mon Sep 17 00:00:00 2001 From: jsy Date: Sun, 6 Dec 2015 02:02:25 +0800 Subject: [PATCH 08/32] 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 09/32] 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 10/32] 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 11/32] 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 12/32] 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 13/32] 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 14/32] 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 15/32] 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 16/32] 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 17/32] 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 18/32] 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 19/32] 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 20/32] 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 21/32] 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 22/32] 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 23/32] 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 24/32] 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 25/32] 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 26/32] 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 27/32] 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 28/32] 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 29/32] 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 30/32] 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 31/32] 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 32/32] 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 +