Browse Source

Finish 3.2.0

add auth_chain_c/d
3.2.0
Akkariiin 7 years ago
parent
commit
0aa49b1ff8
  1. 53
      shadowsocks/asyncdns.py
  2. 166
      shadowsocks/obfsplugin/auth_chain.py
  3. 1
      shadowsocks/obfsplugin/http_simple.py
  4. 12
      shadowsocks/server.py
  5. 12
      shadowsocks/shell.py

53
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}(?<!-)$", re.IGNORECASE)
@ -77,6 +77,7 @@ QTYPE_CNAME = 5
QTYPE_NS = 2
QCLASS_IN = 1
def detect_ipv6_supprot():
if 'has_ipv6' in dir(socket):
try:
@ -89,8 +90,10 @@ def detect_ipv6_supprot():
print('IPv6 not support')
return False
IPV6_CONNECTION_SUPPORT = detect_ipv6_supprot()
def build_address(address):
address = address.strip(b'.')
labels = address.split(b'.')
@ -266,14 +269,22 @@ STATUS_IPV6 = 1
class DNSResolver(object):
def __init__(self):
def __init__(self, black_hostname_list=None):
self._loop = None
self._hosts = {}
self._hostname_status = {}
self._hostname_to_cb = {}
self._cb_to_hostname = {}
self._cache = lru_cache.LRUCache(timeout=300)
# read black_hostname_list from config
if type(black_hostname_list) != list:
self._black_hostname_list = []
else:
self._black_hostname_list = list(map(
(lambda t: t if type(t) == bytes else t.encode('utf8')),
black_hostname_list
))
logging.info('black_hostname_list init as : ' + str(self._black_hostname_list))
self._sock = None
self._servers = None
self._parse_resolv()
@ -462,9 +473,12 @@ 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):
callback(None, Exception('hostname <%s> is block by the black hostname list' % hostname))
return
else:
if not is_valid_hostname(hostname):
callback(None, Exception('invalid hostname: %s' % hostname))
@ -506,7 +520,11 @@ class DNSResolver(object):
def test():
dns_resolver = DNSResolver()
black_hostname_list = [
'baidu.com',
'yahoo.com',
]
dns_resolver = DNSResolver(black_hostname_list=black_hostname_list)
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
@ -521,9 +539,10 @@ def test():
# TODO: what can we assert?
print(result, error)
counter += 1
if counter == 9:
if counter == 12:
dns_resolver.close()
loop.stop()
a_callback = callback
return a_callback
@ -531,6 +550,9 @@ 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())
@ -546,10 +568,25 @@ 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 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 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 len(dns_resolver._black_hostname_list) == 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__':
test()

166
shadowsocks/obfsplugin/auth_chain.py

@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2015-2015 breakwa11
#
@ -38,17 +39,31 @@ 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)
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,),
}
class xorshift128plus(object):
max_int = (1 << 64) - 1
mov_mask = (1 << (64 - 23)) - 1
@ -80,12 +95,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)
@ -121,6 +138,7 @@ class auth_base(plain.plain):
return (b'E' * 2048, False)
return (buf, False)
class client_queue(object):
def __init__(self, begin_id):
self.front = begin_id - 64
@ -175,6 +193,7 @@ class client_queue(object):
self.addref()
return True
class obfs_auth_chain_data(object):
def __init__(self, name):
self.name = name
@ -229,6 +248,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)
@ -362,14 +382,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('<I', uid)[0] ^ struct.unpack('<I', self.last_client_hash[8:12])[0]
uid = struct.pack('<I', uid)
data = uid + encryptor.encrypt(data)[16:]
self.last_server_hash = hmac.new(self.user_key, data, self.hashfunc).digest()
data = check_head + data + self.last_server_hash[:4]
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')
return data + self.pack_client_data(buf)
def auth_data(self):
@ -420,7 +442,8 @@ class auth_chain_a(auth_base):
server_hash = hmac.new(mac_key, self.recv_buf[:length + 2], self.hashfunc).digest()
if server_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])))
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''
raise Exception('client_post_decrypt data uncorrect checksum')
@ -488,7 +511,10 @@ class auth_chain_a(auth_base):
md5data = hmac.new(self.user_key, self.recv_buf[12: 12 + 20], self.hashfunc).digest()
if md5data[:4] != self.recv_buf[32:36]:
logging.error('%s data uncorrect auth HMAC-MD5 from %s:%d, data %s' % (self.no_compatible_method, self.server_info.client, self.server_info.client_port, binascii.hexlify(self.recv_buf)))
logging.error('%s data uncorrect auth HMAC-MD5 from %s:%d, data %s' % (
self.no_compatible_method, self.server_info.client, self.server_info.client_port,
binascii.hexlify(self.recv_buf)
))
if len(self.recv_buf) < 36:
return (b'', False)
return self.not_match_return(self.recv_buf)
@ -503,7 +529,9 @@ class auth_chain_a(auth_base):
connection_id = struct.unpack('<I', head[8:12])[0]
time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff))
if time_dif < -self.max_time_dif or time_dif > 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 +541,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
@ -537,7 +566,9 @@ class auth_chain_a(auth_base):
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])))
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:
@ -577,7 +608,8 @@ class auth_chain_a(auth_base):
uid = struct.unpack('<I', self.user_id)[0] ^ struct.unpack('<I', md5data[:4])[0]
uid = struct.pack('<I', uid)
rand_len = self.udp_rnd_data_len(md5data, self.random_client)
encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4')
encryptor = encrypt.Encryptor(
to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4')
out_buf = encryptor.encrypt(buf)
buf = out_buf + os.urandom(rand_len) + authdata + uid
return buf + hmac.new(self.user_key, buf, self.hashfunc).digest()[:1]
@ -590,7 +622,8 @@ class auth_chain_a(auth_base):
mac_key = self.server_info.key
md5data = hmac.new(mac_key, buf[-8:-1], self.hashfunc).digest()
rand_len = self.udp_rnd_data_len(md5data, self.random_server)
encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4')
encryptor = encrypt.Encryptor(
to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4')
return encryptor.decrypt(buf[:-8 - rand_len])
def server_udp_pre_encrypt(self, buf, uid):
@ -634,11 +667,16 @@ class auth_chain_a(auth_base):
def dispose(self):
self.server_info.data.remove(self.user_id, self.client_id)
class auth_chain_b(auth_chain_a):
def __init__(self, method):
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 +686,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 +712,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
@ -690,3 +736,105 @@ class auth_chain_b(auth_chain_a):
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
# 一定要在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]:
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
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 append item(other time) are not big enough.
# 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)
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

1
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",

12
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:
@ -106,7 +108,8 @@ def main():
(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)

12
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
@ -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,9 @@ 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'] = 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'))
@ -398,6 +402,7 @@ def _decode_dict(data):
rv[key] = value
return rv
class JSFormat:
def __init__(self):
self.state = 0
@ -435,6 +440,7 @@ class JSFormat:
return "\n"
return ""
def remove_comment(json):
fmt = JSFormat()
return "".join([fmt.push(c) for c in json])

Loading…
Cancel
Save