From 15b4d97b6ccabc77ec01375c6e5b0a6de8dfeb32 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 04:14:42 +0800 Subject: [PATCH 01/17] DNSResolver add black_hostname_list TODO read black_hostname_list from config --- shadowsocks/asyncdns.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 797704e..98db3bb 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -27,12 +27,12 @@ import logging if __name__ == '__main__': import sys import inspect + file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) sys.path.insert(0, os.path.join(file_path, '../')) from shadowsocks import common, lru_cache, eventloop, shell - CACHE_SWEEP_INTERVAL = 30 VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d_-]{1,63}(? in the black hostname list' % hostname)) else: if not is_valid_hostname(hostname): callback(None, Exception('invalid hostname: %s' % hostname)) return if False: addrs = socket.getaddrinfo(hostname, 0, 0, - socket.SOCK_DGRAM, socket.SOL_UDP) + socket.SOCK_DGRAM, socket.SOL_UDP) if addrs: af, socktype, proto, canonname, sa = addrs[0] - logging.debug('DNS resolve %s %s' % (hostname, sa[0]) ) + logging.debug('DNS resolve %s %s' % (hostname, sa[0])) self._cache[hostname] = sa[0] callback((hostname, sa[0]), None) return @@ -524,10 +533,11 @@ def test(): if counter == 9: dns_resolver.close() loop.stop() + a_callback = callback return a_callback - assert(make_callback() != make_callback()) + assert (make_callback() != make_callback()) dns_resolver.resolve(b'google.com', make_callback()) dns_resolver.resolve('google.com', make_callback()) @@ -552,4 +562,3 @@ def test(): if __name__ == '__main__': test() - From 813a48f5a7086e899422e94a19c73d1ddda41bcf Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 04:41:05 +0800 Subject: [PATCH 02/17] update DNSResolver black_hostname_list --- shadowsocks/asyncdns.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 98db3bb..899fec9 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -280,7 +280,11 @@ class DNSResolver(object): if type(black_hostname_list) != list: self._black_hostname_list = [] else: - self._black_hostname_list = black_hostname_list + self._black_hostname_list = list(map( + (lambda t: t if type(t) == bytes else t.encode('utf8')), + black_hostname_list + )) + print('black_hostname_list init as : ' + str(self._black_hostname_list)) self._sock = None self._servers = None self._parse_resolv() @@ -474,6 +478,7 @@ class DNSResolver(object): callback((hostname, ip), None) elif hostname in self._black_hostname_list: callback(None, Exception('hostname <%s> in the black hostname list' % hostname)) + return else: if not is_valid_hostname(hostname): callback(None, Exception('invalid hostname: %s' % hostname)) @@ -515,7 +520,10 @@ class DNSResolver(object): def test(): - dns_resolver = DNSResolver() + black_hostname_list = [ + 'baidu.com' + ] + dns_resolver = DNSResolver(black_hostname_list=black_hostname_list) loop = eventloop.EventLoop() dns_resolver.add_to_loop(loop) @@ -541,6 +549,7 @@ def test(): dns_resolver.resolve(b'google.com', make_callback()) dns_resolver.resolve('google.com', make_callback()) + dns_resolver.resolve('baidu.com', make_callback()) dns_resolver.resolve('example.com', make_callback()) dns_resolver.resolve('ipv6.google.com', make_callback()) dns_resolver.resolve('www.facebook.com', make_callback()) @@ -556,8 +565,24 @@ def test(): 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 'long.hostname', make_callback()) - loop.run() + # test black_hostname_list + dns_resolver = DNSResolver(black_hostname_list=[]) + assert type(dns_resolver._black_hostname_list) == list + assert dns_resolver._black_hostname_list.__len__() == 0 + dns_resolver.close() + dns_resolver = DNSResolver(black_hostname_list=123) + assert type(dns_resolver._black_hostname_list) == list + assert dns_resolver._black_hostname_list.__len__() == 0 + dns_resolver.close() + dns_resolver = DNSResolver(black_hostname_list=None) + assert type(dns_resolver._black_hostname_list) == list + assert dns_resolver._black_hostname_list.__len__() == 0 + dns_resolver.close() + dns_resolver = DNSResolver() + assert type(dns_resolver._black_hostname_list) == list + assert dns_resolver._black_hostname_list.__len__() == 0 + dns_resolver.close() if __name__ == '__main__': From 2d1eeb5c85c0f393c1d29a8c47bb7e71ab886bb2 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 04:58:07 +0800 Subject: [PATCH 03/17] update DNSResolver black_hostname_list --- shadowsocks/asyncdns.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 899fec9..316c6d1 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -476,8 +476,8 @@ class DNSResolver(object): logging.debug('hit cache: %s', hostname) ip = self._cache[hostname] callback((hostname, ip), None) - elif hostname in self._black_hostname_list: - callback(None, Exception('hostname <%s> in the black hostname list' % hostname)) + elif any(hostname.endswith(t) for t in self._black_hostname_list): + callback(None, Exception('hostname <%s> is block by the black hostname list' % hostname)) return else: if not is_valid_hostname(hostname): @@ -521,7 +521,8 @@ class DNSResolver(object): def test(): black_hostname_list = [ - 'baidu.com' + 'baidu.com', + 'yahoo.com', ] dns_resolver = DNSResolver(black_hostname_list=black_hostname_list) loop = eventloop.EventLoop() @@ -538,7 +539,7 @@ def test(): # TODO: what can we assert? print(result, error) counter += 1 - if counter == 9: + if counter == 12: dns_resolver.close() loop.stop() @@ -550,6 +551,8 @@ def test(): dns_resolver.resolve(b'google.com', make_callback()) dns_resolver.resolve('google.com', make_callback()) dns_resolver.resolve('baidu.com', make_callback()) + dns_resolver.resolve('map.baidu.com', make_callback()) + dns_resolver.resolve('yahoo.com', make_callback()) dns_resolver.resolve('example.com', make_callback()) dns_resolver.resolve('ipv6.google.com', make_callback()) dns_resolver.resolve('www.facebook.com', make_callback()) From a1b2513a82c8f3b2ae8ba013be001f4ad8c8e487 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 04:58:34 +0800 Subject: [PATCH 04/17] read black_hostname_list from config --- shadowsocks/shell.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 6246d98..37bafef 100755 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -26,7 +26,6 @@ import logging from shadowsocks.common import to_bytes, to_str, IPNetwork, PortRange from shadowsocks import encrypt - VERBOSE_LEVEL = 5 verbose = 0 @@ -52,6 +51,7 @@ def print_exception(e): import traceback traceback.print_exc() + def __version(): version_str = '' try: @@ -65,9 +65,11 @@ def __version(): pass return version_str + def print_shadowsocks(): print('ShadowsocksR %s' % __version()) + def log_shadowsocks_version(): logging.info('ShadowsocksR %s' % __version()) @@ -84,6 +86,7 @@ def find_config(): return sub_find(user_config_path) or sub_find(config_path) + def check_config(config, is_local): if config.get('daemon', None) == 'stop': # no need to specify configuration for daemon stop @@ -110,13 +113,13 @@ def check_config(config, is_local): logging.warning('warning: local set to listen on 0.0.0.0, it\'s not safe') if config.get('server', '') in ['127.0.0.1', 'localhost']: logging.warning('warning: server set to listen on %s:%s, are you sure?' % - (to_str(config['server']), config['server_port'])) + (to_str(config['server']), config['server_port'])) if config.get('timeout', 300) < 100: logging.warning('warning: your timeout %d seems too short' % - int(config.get('timeout'))) + int(config.get('timeout'))) if config.get('timeout', 300) > 600: logging.warning('warning: your timeout %d seems too long' % - int(config.get('timeout'))) + int(config.get('timeout'))) if config.get('password') in [b'mypassword']: logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 'config.json!') @@ -160,7 +163,6 @@ def get_config(is_local): if config_path is None: config_path = find_config() - if config_path: logging.debug('loading config from %s' % config_path) with open(config_path, 'rb') as f: @@ -170,7 +172,6 @@ def get_config(is_local): logging.error('found an error in config.json: %s', str(e)) sys.exit(1) - v_count = 0 for key, value in optlist: if key == '-p': @@ -260,6 +261,7 @@ def get_config(is_local): config['server'] = to_str(config['server']) else: config['server'] = to_str(config.get('server', '0.0.0.0')) + config['black_hostname_list'] = config.get('black_hostname_list', []) try: config['forbidden_ip'] = \ IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) @@ -398,6 +400,7 @@ def _decode_dict(data): rv[key] = value return rv + class JSFormat: def __init__(self): self.state = 0 @@ -435,6 +438,7 @@ class JSFormat: return "\n" return "" + def remove_comment(json): fmt = JSFormat() return "".join([fmt.push(c) for c in json]) From 94d720bcb79154cc6055885f9e50db80b3b05a16 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 05:05:17 +0800 Subject: [PATCH 05/17] fix read black_hostname_list from config black_hostname_list in config file format is like forbidden_port --- shadowsocks/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 37bafef..78ba333 100755 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -261,7 +261,7 @@ def get_config(is_local): config['server'] = to_str(config['server']) else: config['server'] = to_str(config.get('server', '0.0.0.0')) - config['black_hostname_list'] = config.get('black_hostname_list', []) + config['black_hostname_list'] = to_str(config.get('black_hostname_list', '')).split(',') try: config['forbidden_ip'] = \ IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) From 2db97259a07b95ff8e8f6807de97c41df3852479 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 12:32:21 +0800 Subject: [PATCH 06/17] TODO user config user_agent --- shadowsocks/obfsplugin/http_simple.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowsocks/obfsplugin/http_simple.py b/shadowsocks/obfsplugin/http_simple.py index 6f1a05e..ff3c5fd 100644 --- a/shadowsocks/obfsplugin/http_simple.py +++ b/shadowsocks/obfsplugin/http_simple.py @@ -63,6 +63,7 @@ class http_simple(plain.plain): self.host = None self.port = 0 self.recv_buffer = b'' + # TODO user config user_agent self.user_agent = [b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0", b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0", b"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", From 576516223632feafa9c99ac15676d559fbb3f64f Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Sat, 29 Jul 2017 13:33:33 +0800 Subject: [PATCH 07/17] black_hostname_list to DNSResolver --- shadowsocks/server.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index c18ad1c..0815389 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -25,6 +25,7 @@ import signal if __name__ == '__main__': import inspect + file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) sys.path.insert(0, os.path.join(file_path, '../')) @@ -43,7 +44,8 @@ def main(): try: import resource - logging.info('current process RLIMIT_NOFILE resource: soft %d hard %d' % resource.getrlimit(resource.RLIMIT_NOFILE)) + logging.info( + 'current process RLIMIT_NOFILE resource: soft %d hard %d' % resource.getrlimit(resource.RLIMIT_NOFILE)) except ImportError: pass @@ -68,7 +70,7 @@ def main(): tcp_servers = [] udp_servers = [] - dns_resolver = asyncdns.DNSResolver() + dns_resolver = asyncdns.DNSResolver(config['black_hostname_list']) if int(config['workers']) > 1: stat_counter_dict = None else: @@ -103,10 +105,11 @@ def main(): a_config = config.copy() ipv6_ok = False logging.info("server start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" % - (protocol, password, method, obfs, obfs_param)) + (protocol, password, method, obfs, obfs_param)) if 'server_ipv6' in a_config: try: - if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][-1] == "]": + if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][ + -1] == "]": a_config['server_ipv6'] = a_config['server_ipv6'][1:-1] a_config['server_port'] = int(port) a_config['password'] = password @@ -151,11 +154,13 @@ def main(): logging.warn('received SIGQUIT, doing graceful shutting down..') list(map(lambda s: s.close(next_tick=True), tcp_servers + udp_servers)) + signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), child_handler) def int_handler(signum, _): sys.exit(1) + signal.signal(signal.SIGINT, int_handler) try: @@ -191,6 +196,7 @@ def main(): except OSError: # child may already exited pass sys.exit() + signal.signal(signal.SIGTERM, handler) signal.signal(signal.SIGQUIT, handler) signal.signal(signal.SIGINT, handler) From 6c89bd405105b7dccf813377dbc2a271385a7fc6 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Mon, 31 Jul 2017 00:32:17 +0800 Subject: [PATCH 08/17] add note for chain_b --- shadowsocks/obfsplugin/auth_chain.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index 26097bf..cf2b341 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -639,6 +639,10 @@ class auth_chain_b(auth_chain_a): super(auth_chain_b, self).__init__(method) self.salt = b"auth_chain_b" self.no_compatible_method = 'auth_chain_b' + # NOTE + # 补全后长度数组 + # 随机在其中选择一个补全到的长度 + # 为每个连接初始化一个固定内容的数组 self.data_size_list = [] self.data_size_list2 = [] @@ -648,10 +652,12 @@ class auth_chain_b(auth_chain_a): self.data_size_list2 = [] random = xorshift128plus() random.init_from_bin(key) + # 补全数组长为4~12-1 list_len = random.next() % 8 + 4 for i in range(0, list_len): self.data_size_list.append((int)(random.next() % 2340 % 2040 % 1440)) self.data_size_list.sort() + # 补全数组长为8~24-1 list_len = random.next() % 16 + 8 for i in range(0, list_len): self.data_size_list2.append((int)(random.next() % 2340 % 2040 % 1440)) @@ -672,15 +678,21 @@ class auth_chain_b(auth_chain_a): random.init_from_bin_len(last_hash, buf_size) pos = bisect.bisect_left(self.data_size_list, buf_size + self.server_info.overhead) final_pos = pos + random.next() % (len(self.data_size_list)) + # 假设random均匀分布,则越长的原始数据长度越容易if false if final_pos < len(self.data_size_list): return self.data_size_list[final_pos] - buf_size - self.server_info.overhead + # 上面if false后选择2号补全数组,此处有更精细的长度分段 pos = bisect.bisect_left(self.data_size_list2, buf_size + self.server_info.overhead) final_pos = pos + random.next() % (len(self.data_size_list2)) if final_pos < len(self.data_size_list2): return self.data_size_list2[final_pos] - buf_size - self.server_info.overhead + # final_pos 总是分布在pos~(data_size_list2.len-1)之间 if final_pos < pos + len(self.data_size_list2) - 1: return 0 + # 有1/len(self.data_size_list2)的概率不满足上一个if ? + # 理论上不会运行到此处,因此可以插入运行断言 ? + # assert False if buf_size > 1300: return random.next() % 31 From 7437b058cc1c4e77198207a2ccbe081223b63fb0 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Mon, 31 Jul 2017 00:32:40 +0800 Subject: [PATCH 09/17] update dns test to use len() --- shadowsocks/asyncdns.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 316c6d1..04e547d 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -572,15 +572,15 @@ def test(): # test black_hostname_list dns_resolver = DNSResolver(black_hostname_list=[]) assert type(dns_resolver._black_hostname_list) == list - assert dns_resolver._black_hostname_list.__len__() == 0 + assert len(dns_resolver._black_hostname_list) == 0 dns_resolver.close() dns_resolver = DNSResolver(black_hostname_list=123) assert type(dns_resolver._black_hostname_list) == list - assert dns_resolver._black_hostname_list.__len__() == 0 + assert len(dns_resolver._black_hostname_list) == 0 dns_resolver.close() dns_resolver = DNSResolver(black_hostname_list=None) assert type(dns_resolver._black_hostname_list) == list - assert dns_resolver._black_hostname_list.__len__() == 0 + assert len(dns_resolver._black_hostname_list) == 0 dns_resolver.close() dns_resolver = DNSResolver() assert type(dns_resolver._black_hostname_list) == list From 2f012ab04f3d98f480961aa8bb2c935fb82e2730 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Tue, 1 Aug 2017 19:20:47 +0800 Subject: [PATCH 10/17] format code --- shadowsocks/obfsplugin/auth_chain.py | 80 ++++++++++++++++++---------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index cf2b341..3cff2ec 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -38,17 +38,21 @@ from shadowsocks import common, lru_cache, encrypt from shadowsocks.obfsplugin import plain from shadowsocks.common import to_bytes, to_str, ord, chr + def create_auth_chain_a(method): return auth_chain_a(method) + def create_auth_chain_b(method): return auth_chain_b(method) + obfs_map = { - 'auth_chain_a': (create_auth_chain_a,), - 'auth_chain_b': (create_auth_chain_b,), + 'auth_chain_a': (create_auth_chain_a,), + 'auth_chain_b': (create_auth_chain_b,), } + class xorshift128plus(object): max_int = (1 << 64) - 1 mov_mask = (1 << (64 - 23)) - 1 @@ -80,12 +84,14 @@ class xorshift128plus(object): for i in range(4): self.next() + def match_begin(str1, str2): if len(str1) >= len(str2): if str1[:len(str2)] == str2: return True return False + class auth_base(plain.plain): def __init__(self, method): super(auth_base, self).__init__(method) @@ -96,7 +102,7 @@ class auth_base(plain.plain): def init_data(self): return '' - def get_overhead(self, direction): # direction: true for c->s false for s->c + def get_overhead(self, direction): # direction: true for c->s false for s->c return self.overhead def set_server_info(self, server_info): @@ -118,9 +124,10 @@ class auth_base(plain.plain): self.raw_trans = True self.overhead = 0 if self.method == self.no_compatible_method: - return (b'E'*2048, False) + return (b'E' * 2048, False) return (buf, False) + class client_queue(object): def __init__(self, begin_id): self.front = begin_id - 64 @@ -175,13 +182,14 @@ class client_queue(object): self.addref() return True + class obfs_auth_chain_data(object): def __init__(self, name): self.name = name self.user_id = {} self.local_client_id = b'' self.connection_id = 0 - self.set_max_client(64) # max active client count + self.set_max_client(64) # max active client count def update(self, user_id, client_id, connection_id): if user_id not in self.user_id: @@ -203,7 +211,7 @@ class obfs_auth_chain_data(object): if local_client_id.get(client_id, None) is None or not local_client_id[client_id].enable: if local_client_id.first() is None or len(local_client_id) < self.max_client: if client_id not in local_client_id: - #TODO: check + # TODO: check local_client_id[client_id] = client_queue(connection_id) else: local_client_id[client_id].re_enable(connection_id) @@ -212,7 +220,7 @@ class obfs_auth_chain_data(object): if not local_client_id[local_client_id.first()].is_active(): del local_client_id[local_client_id.first()] if client_id not in local_client_id: - #TODO: check + # TODO: check local_client_id[client_id] = client_queue(connection_id) else: local_client_id[client_id].re_enable(connection_id) @@ -229,6 +237,7 @@ class obfs_auth_chain_data(object): if client_id in local_client_id: local_client_id[client_id].delref() + class auth_chain_a(auth_base): def __init__(self, method): super(auth_chain_a, self).__init__(method) @@ -240,7 +249,7 @@ class auth_chain_a(auth_base): self.has_recv_header = False self.client_id = 0 self.connection_id = 0 - self.max_time_dif = 60 * 60 * 24 # time dif (second) setting + self.max_time_dif = 60 * 60 * 24 # time dif (second) setting self.salt = b"auth_chain_a" self.no_compatible_method = 'auth_chain_a' self.pack_id = 1 @@ -259,7 +268,7 @@ class auth_chain_a(auth_base): def init_data(self): return obfs_auth_chain_data(self.method) - def get_overhead(self, direction): # direction: true for c->s false for s->c + def get_overhead(self, direction): # direction: true for c->s false for s->c return self.overhead def set_server_info(self, server_info): @@ -362,14 +371,16 @@ class auth_chain_a(auth_base): if self.user_key is None: self.user_key = self.server_info.key - encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc', b'\x00' * 16) + encryptor = encrypt.Encryptor( + to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc', b'\x00' * 16) uid = struct.unpack(' 0 and rand_len > 0: pos = 2 + self.rnd_start_pos(rand_len, self.random_server) - out_buf += self.encryptor.decrypt(self.recv_buf[pos : data_len + pos]) + out_buf += self.encryptor.decrypt(self.recv_buf[pos: data_len + pos]) self.last_server_hash = server_hash if self.recv_id == 1: self.server_info.tcp_mss = struct.unpack(' self.max_time_dif: - logging.info('%s: wrong timestamp, time_dif %d, data %s' % (self.no_compatible_method, time_dif, binascii.hexlify(head))) + logging.info('%s: wrong timestamp, time_dif %d, data %s' % ( + self.no_compatible_method, time_dif, binascii.hexlify(head) + )) return self.not_match_return(self.recv_buf) elif self.server_info.data.insert(self.user_id, client_id, connection_id): self.has_recv_header = True @@ -513,7 +530,8 @@ class auth_chain_a(auth_base): logging.info('%s: auth fail, data %s' % (self.no_compatible_method, binascii.hexlify(out_buf))) return self.not_match_return(self.recv_buf) - self.encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(self.last_client_hash)), 'rc4') + self.encryptor = encrypt.Encryptor( + to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(self.last_client_hash)), 'rc4') self.recv_buf = self.recv_buf[36:] self.has_recv_header = True sendback = True @@ -528,7 +546,7 @@ class auth_chain_a(auth_base): self.recv_buf = b'' if self.recv_id == 0: logging.info(self.no_compatible_method + ': over size') - return (b'E'*2048, False) + return (b'E' * 2048, False) else: raise Exception('server_post_decrype data error') @@ -536,12 +554,14 @@ class auth_chain_a(auth_base): break client_hash = hmac.new(mac_key, self.recv_buf[:length + 2], self.hashfunc).digest() - if client_hash[:2] != self.recv_buf[length + 2 : length + 4]: - logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) + if client_hash[:2] != self.recv_buf[length + 2: length + 4]: + logging.info('%s: checksum error, data %s' % ( + self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]) + )) self.raw_trans = True self.recv_buf = b'' if self.recv_id == 0: - return (b'E'*2048, False) + return (b'E' * 2048, False) else: raise Exception('server_post_decrype data uncorrect checksum') @@ -549,7 +569,7 @@ class auth_chain_a(auth_base): pos = 2 if data_len > 0 and rand_len > 0: pos = 2 + self.rnd_start_pos(rand_len, self.random_client) - out_buf += self.encryptor.decrypt(self.recv_buf[pos : data_len + pos]) + out_buf += self.encryptor.decrypt(self.recv_buf[pos: data_len + pos]) self.last_client_hash = client_hash self.recv_buf = self.recv_buf[length + 4:] if data_len == 0: @@ -577,7 +597,8 @@ class auth_chain_a(auth_base): uid = struct.unpack(' 400: return random.next() % 521 return random.next() % 1021 - From b07853846decdf675eca7bbf8e39689cb66d7134 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Wed, 2 Aug 2017 00:51:47 +0800 Subject: [PATCH 11/17] add auth_chain_c and auth_chain_d --- shadowsocks/obfsplugin/auth_chain.py | 112 +++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index 3cff2ec..f219eac 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -47,9 +47,19 @@ def create_auth_chain_b(method): return auth_chain_b(method) +def create_auth_chain_c(method): + return auth_chain_c(method) + + +def create_auth_chain_d(method): + return auth_chain_d(method) + + obfs_map = { 'auth_chain_a': (create_auth_chain_a,), 'auth_chain_b': (create_auth_chain_b,), + 'auth_chain_c': (create_auth_chain_c,), + 'auth_chain_d': (create_auth_chain_d,), } @@ -724,3 +734,105 @@ class auth_chain_b(auth_chain_a): if buf_size > 400: return random.next() % 521 return random.next() % 1021 + + +class auth_chain_c(auth_chain_b): + def __init__(self, method): + super(auth_chain_c, self).__init__(method) + self.salt = b"auth_chain_c" + self.no_compatible_method = 'auth_chain_c' + self.data_size_list0 = [] + + def init_data_size(self, key): + if self.data_size_list0: + self.data_size_list0 = [] + random = xorshift128plus() + random.init_from_bin(key) + # 补全数组长为12~24-1 + list_len = random.next() % (8 + 16) + (4 + 8) + for i in range(0, list_len): + self.data_size_list0.append((int)(random.next() % 2340 % 2040 % 1440)) + self.data_size_list0.sort() + + def set_server_info(self, server_info): + self.server_info = server_info + try: + max_client = int(server_info.protocol_param.split('#')[0]) + except: + max_client = 64 + self.server_info.data.set_max_client(max_client) + self.init_data_size(self.server_info.key) + + def rnd_data_len(self, buf_size, last_hash, random): + other_data_size = buf_size + self.server_info.overhead + # final_pos 总是分布在pos~(data_size_list0.len-1)之间 + # 除非data_size_list0中的任何值均过小使其全部都无法容纳buf + if other_data_size >= self.data_size_list0[-1]: + if other_data_size >= 1440: + return 0 + if other_data_size > 1300: + return random.next() % 31 + if other_data_size > 900: + return random.next() % 127 + if other_data_size > 400: + return random.next() % 521 + return random.next() % 1021 + + random.init_from_bin_len(last_hash, buf_size) + pos = bisect.bisect_left(self.data_size_list0, other_data_size) + # random select a size in the leftover data_size_list0 + final_pos = pos + random.next() % (len(self.data_size_list0) - pos) + return self.data_size_list0[final_pos] - other_data_size + + +class auth_chain_d(auth_chain_b): + def __init__(self, method): + super(auth_chain_d, self).__init__(method) + self.salt = b"auth_chain_d" + self.no_compatible_method = 'auth_chain_d' + self.data_size_list0 = [] + + def check_and_patch_data_size(self, random): + # append new item + # when the biggest item(first time) or the last item(other time) are not big enough. + # but set a limit size (64) to avoid stack over follow. + if self.data_size_list0[-1] < 1000 and len(self.data_size_list0) < 64: + self.data_size_list0.append((int)(random.next() % 2340 % 2040 % 1440)) + self.check_and_patch_data_size(random) + + def init_data_size(self, key): + if self.data_size_list0: + self.data_size_list0 = [] + random = xorshift128plus() + random.init_from_bin(key) + # 补全数组长为12~24-1 + list_len = random.next() % (8 + 16) + (4 + 8) + for i in range(0, list_len): + self.data_size_list0.append((int)(random.next() % 2340 % 2040 % 1440)) + self.data_size_list0.sort() + old_len = len(self.data_size_list0) + self.check_and_patch_data_size(random) + # if check_and_patch_data_size are work, re-sort again. + if old_len != len(self.data_size_list0): + self.data_size_list0.sort() + + def set_server_info(self, server_info): + self.server_info = server_info + try: + max_client = int(server_info.protocol_param.split('#')[0]) + except: + max_client = 64 + self.server_info.data.set_max_client(max_client) + self.init_data_size(self.server_info.key) + + def rnd_data_len(self, buf_size, last_hash, random): + other_data_size = buf_size + self.server_info.overhead + # if other_data_size > the bigest item in data_size_list0, not padding any data + if other_data_size >= self.data_size_list0[-1]: + return 0 + + random.init_from_bin_len(last_hash, buf_size) + pos = bisect.bisect_left(self.data_size_list0, other_data_size) + # random select a size in the leftover data_size_list0 + final_pos = pos + random.next() % (len(self.data_size_list0) - pos) + return self.data_size_list0[final_pos] - other_data_size From d07a3946d919fcaad3a1e410f5ca29b0cc7befad Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Wed, 2 Aug 2017 00:56:11 +0800 Subject: [PATCH 12/17] change auth_chain_d biggest size value --- shadowsocks/obfsplugin/auth_chain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index f219eac..7a6289f 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -794,9 +794,9 @@ class auth_chain_d(auth_chain_b): def check_and_patch_data_size(self, random): # append new item - # when the biggest item(first time) or the last item(other time) are not big enough. + # when the biggest item(first time) or the last append item(other time) are not big enough. # but set a limit size (64) to avoid stack over follow. - if self.data_size_list0[-1] < 1000 and len(self.data_size_list0) < 64: + if self.data_size_list0[-1] < 1300 and len(self.data_size_list0) < 64: self.data_size_list0.append((int)(random.next() % 2340 % 2040 % 1440)) self.check_and_patch_data_size(random) From 41b7e1e4c0abd90a78f6c0a6f219a36e55416647 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Wed, 2 Aug 2017 18:48:51 +0800 Subject: [PATCH 13/17] fix typo --- shadowsocks/obfsplugin/auth_chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index 7a6289f..9b0de60 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -795,7 +795,7 @@ class auth_chain_d(auth_chain_b): def check_and_patch_data_size(self, random): # append new item # when the biggest item(first time) or the last append item(other time) are not big enough. - # but set a limit size (64) to avoid stack over follow. + # but set a limit size (64) to avoid stack overflow. if self.data_size_list0[-1] < 1300 and len(self.data_size_list0) < 64: self.data_size_list0.append((int)(random.next() % 2340 % 2040 % 1440)) self.check_and_patch_data_size(random) From a6c1f6952b6f7a43e7834b450c8624ee3d7c8ba0 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Thu, 3 Aug 2017 11:36:13 +0800 Subject: [PATCH 14/17] fix issue in auth_chain_c: cannot find payload data issue which case by not re-init random object --- shadowsocks/obfsplugin/auth_chain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index 9b0de60..91d75f9 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -765,6 +765,8 @@ class auth_chain_c(auth_chain_b): def rnd_data_len(self, buf_size, last_hash, random): other_data_size = buf_size + self.server_info.overhead + # 一定要在random使用前初始化,以保证服务器与客户端同步,保证包大小验证结果正确 + random.init_from_bin_len(last_hash, buf_size) # final_pos 总是分布在pos~(data_size_list0.len-1)之间 # 除非data_size_list0中的任何值均过小使其全部都无法容纳buf if other_data_size >= self.data_size_list0[-1]: @@ -778,7 +780,6 @@ class auth_chain_c(auth_chain_b): return random.next() % 521 return random.next() % 1021 - random.init_from_bin_len(last_hash, buf_size) pos = bisect.bisect_left(self.data_size_list0, other_data_size) # random select a size in the leftover data_size_list0 final_pos = pos + random.next() % (len(self.data_size_list0) - pos) From f412e6da97b373b470092d46c28953d70a3b3bb3 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Mon, 7 Aug 2017 13:22:04 +0800 Subject: [PATCH 15/17] add coding: utf-8 to let py support chinese --- shadowsocks/obfsplugin/auth_chain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index 91d75f9..b11d545 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # # Copyright 2015-2015 breakwa11 # From 94cb1aaaa3615afee569a0ba1fd2b1a3bd5a4ed6 Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Mon, 7 Aug 2017 13:38:14 +0800 Subject: [PATCH 16/17] fix black_hostname_list work error issue when config black_hostname_list is empty --- shadowsocks/asyncdns.py | 2 +- shadowsocks/shell.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 04e547d..04444da 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -276,7 +276,7 @@ class DNSResolver(object): self._hostname_to_cb = {} self._cb_to_hostname = {} self._cache = lru_cache.LRUCache(timeout=300) - # TODO read black_hostname_list from config + # read black_hostname_list from config if type(black_hostname_list) != list: self._black_hostname_list = [] else: diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 78ba333..a1547d0 100755 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -262,6 +262,8 @@ def get_config(is_local): else: config['server'] = to_str(config.get('server', '0.0.0.0')) config['black_hostname_list'] = to_str(config.get('black_hostname_list', '')).split(',') + if len(config['black_hostname_list']) == 1 and config['black_hostname_list'][0] == '': + config['black_hostname_list'] = [] try: config['forbidden_ip'] = \ IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) From 6792c15a7da7b36f17b4140542da63dc884cc1dd Mon Sep 17 00:00:00 2001 From: Akkariiin Date: Mon, 7 Aug 2017 14:34:11 +0800 Subject: [PATCH 17/17] change log info --- shadowsocks/asyncdns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 04444da..868ea61 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -284,7 +284,7 @@ class DNSResolver(object): (lambda t: t if type(t) == bytes else t.encode('utf8')), black_hostname_list )) - print('black_hostname_list init as : ' + str(self._black_hostname_list)) + logging.info('black_hostname_list init as : ' + str(self._black_hostname_list)) self._sock = None self._servers = None self._parse_resolv() @@ -473,7 +473,7 @@ class DNSResolver(object): ip = self._hosts[hostname] callback((hostname, ip), None) elif hostname in self._cache: - logging.debug('hit cache: %s', hostname) + logging.debug('hit cache: %s ==>> %s', hostname, self._cache[hostname]) ip = self._cache[hostname] callback((hostname, ip), None) elif any(hostname.endswith(t) for t in self._black_hostname_list):