Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51c8ae4d78 | ||
|
|
862fa890ee | ||
|
|
d82c931f8e | ||
|
|
8e27035851 | ||
|
|
4d9fca9b37 | ||
|
|
90ffd1c6ee | ||
|
|
922cf462d6 | ||
|
|
76fd4296a5 | ||
|
|
63cceb29f9 | ||
|
|
1b1c1fbac6 | ||
|
|
6cbcd855ff | ||
|
|
bf0055be40 | ||
|
|
63a6de5d26 | ||
|
|
1c58b10dd0 | ||
|
|
6712dd62c0 | ||
|
|
caaacc7090 | ||
|
|
6f35105ea2 | ||
|
|
e19f9e70e7 | ||
|
|
e3d6efbc7a | ||
|
|
1355c273db | ||
|
|
bae7d7a0c2 | ||
|
|
e1b199fc44 | ||
|
|
e2957e672e | ||
|
|
f296751def | ||
|
|
c4c1cd6dc1 | ||
|
|
74083af472 | ||
|
|
6732244ddf | ||
|
|
2723295101 | ||
|
|
85955d6e6d | ||
|
|
72ca31dad9 | ||
|
|
2750af4a2e | ||
|
|
bf8bb8afe2 | ||
|
|
5d479087a8 | ||
|
|
26b4d8bc18 | ||
|
|
3014b0034c | ||
|
|
bf443d72ec | ||
|
|
31aaea603a | ||
|
|
390be3bf8d | ||
|
|
e7a61a934f | ||
|
|
96760ed438 | ||
|
|
00a31285ed | ||
|
|
68693d5ec7 | ||
|
|
7cfbe49806 | ||
|
|
9c794624e0 | ||
|
|
ccacef1556 | ||
|
|
75643635d4 | ||
|
|
4048abaeff | ||
|
|
d4b7483ef0 | ||
|
|
ad892e1223 | ||
|
|
2de2a553d7 | ||
|
|
588d8ad8b3 | ||
|
|
278ce30c66 | ||
|
|
25c66dab14 | ||
|
|
de67c11207 | ||
|
|
4f52dffcc1 | ||
|
|
cd825c9c2c | ||
|
|
e6410c067b | ||
|
|
f0b0a31baf | ||
|
|
6d53546f6e | ||
|
|
8777152ec9 | ||
|
|
f05bea4e10 | ||
|
|
0bf1e46ace | ||
|
|
0f8dd36297 | ||
|
|
498f71a21b | ||
|
|
490ae12128 | ||
|
|
84fd119f26 | ||
|
|
fd9214b334 | ||
|
|
b367ad0560 | ||
|
|
0ca45be89d | ||
|
|
959aad3f41 |
@@ -29,3 +29,8 @@ htmlcov
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
# Ignore user config
|
||||
user-config.json
|
||||
userapiconfig.py
|
||||
usermysql.json
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
3.1.2 2017-04-07
|
||||
- display UID
|
||||
- auto adjust TCP MSS
|
||||
|
||||
3.1.1 2017-03-25
|
||||
- add "New session ticket"
|
||||
- ignore bind 10.0.0.0/8 and 192.168.0.0/16 by default
|
||||
- improve rand size under auth_aes128_*
|
||||
- fix bugs
|
||||
|
||||
3.1.0 2017-03-16
|
||||
- add "glzjinmod" interface
|
||||
- rate limit
|
||||
- add additional_ports in config
|
||||
|
||||
3.0.4 2017-01-08
|
||||
- multi-user in single port
|
||||
|
||||
3.0.1 2017-01-03
|
||||
- remove auth_aes128_*_compatible
|
||||
|
||||
|
||||
+5
-5
@@ -3,9 +3,9 @@ FROM alpine
|
||||
ENV SERVER_ADDR 0.0.0.0
|
||||
ENV SERVER_PORT 51348
|
||||
ENV PASSWORD=
|
||||
ENV METHOD aes-256-cfb
|
||||
ENV PROTOCOL origin
|
||||
ENV OBFS http_simple
|
||||
ENV METHOD aes-128-ctr
|
||||
ENV PROTOCOL auth_aes128_md5
|
||||
ENV OBFS tls1.2_ticket_auth_compatible
|
||||
ENV TIMEOUT 300
|
||||
ENV DNS_ADDR 8.8.8.8
|
||||
ENV DNS_ADDR_2 8.8.4.4
|
||||
@@ -22,11 +22,11 @@ RUN apk update \
|
||||
|
||||
RUN wget --no-check-certificate https://github.com/breakwa11/shadowsocks/archive/manyuser.zip -O /tmp/manyuser.zip \
|
||||
&& unzip -d /tmp /tmp/manyuser.zip \
|
||||
&& mv /tmp/shadowsocks-manyuser/shadowsocks ~/shadowsocks \
|
||||
&& mv /tmp/shadowsocksr-manyuser/shadowsocks ~/shadowsocks \
|
||||
&& rm -rf /tmp/*
|
||||
|
||||
|
||||
WORKDIR ~/shadowsocks
|
||||
|
||||
|
||||
CMD python ~/shadowsocks/server.py -p $SERVER_PORT -k $PASSWORD -m $METHOD -O $PROTOCOL -o $OBFS
|
||||
CMD python ~/shadowsocks/server.py -p $SERVER_PORT -k $PASSWORD -m $METHOD -O $PROTOCOL -o $OBFS
|
||||
|
||||
@@ -33,11 +33,11 @@ move to "~/shadowsocksr", then run:
|
||||
|
||||
move to "~/shadowsocksr/shadowsocks", then run:
|
||||
|
||||
python server.py -p 443 -k password -m aes-128-cfb -O auth_aes128_md5_compatible -o tls1.2_ticket_auth_compatible
|
||||
python server.py -p 443 -k password -m aes-128-cfb -O auth_aes128_md5 -o tls1.2_ticket_auth_compatible
|
||||
|
||||
Check all the options via `-h`.
|
||||
|
||||
You can also use a configuration file instead (recommand), move to "~/shadowsocksr" and edit the file "user-config.json", then move to "~/shadowsocksr/shadowsocks" again, just run:
|
||||
You can also use a configuration file instead (recommend), move to "~/shadowsocksr" and edit the file "user-config.json", then move to "~/shadowsocksr/shadowsocks" again, just run:
|
||||
|
||||
python server.py
|
||||
|
||||
@@ -57,7 +57,7 @@ To monitor the log:
|
||||
Client
|
||||
------
|
||||
|
||||
* [Windows] / [OS X]
|
||||
* [Windows] / [macOS]
|
||||
* [Android] / [iOS]
|
||||
* [OpenWRT]
|
||||
|
||||
@@ -99,7 +99,7 @@ Bugs and Issues
|
||||
[iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help
|
||||
[Issue Tracker]: https://github.com/shadowsocksr/shadowsocksr/issues?state=open
|
||||
[OpenWRT]: https://github.com/shadowsocks/openwrt-shadowsocks
|
||||
[OS X]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help
|
||||
[macOS]: https://github.com/shadowsocksr/ShadowsocksX-NG
|
||||
[Travis CI]: https://travis-ci.org/shadowsocksr/shadowsocksr
|
||||
[Windows]: https://github.com/shadowsocksr/shadowsocksr-csharp
|
||||
[Wiki]: https://github.com/breakwa11/shadowsocks-rss/wiki
|
||||
[Wiki]: https://github.com/breakwa11/shadowsocks-rss/wiki
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Config
|
||||
API_INTERFACE = 'sspanelv2' #mudbjson, sspanelv2, sspanelv3, sspanelv3ssr, muapiv2(not support)
|
||||
API_INTERFACE = 'sspanelv2' #mudbjson, sspanelv2, sspanelv3, sspanelv3ssr, glzjinmod, muapiv2(not support)
|
||||
UPDATE_TIME = 60
|
||||
SERVER_PUB_ADDR = '127.0.0.1' # mujson_mgr need this to generate ssr link
|
||||
|
||||
|
||||
+8
-3
@@ -4,14 +4,19 @@
|
||||
"server_port": 8388,
|
||||
"local_address": "127.0.0.1",
|
||||
"local_port": 1080,
|
||||
|
||||
"password": "m",
|
||||
"timeout": 120,
|
||||
"udp_timeout": 60,
|
||||
"method": "aes-256-cfb",
|
||||
"method": "aes-128-ctr",
|
||||
"protocol": "auth_aes128_md5",
|
||||
"protocol_param": "",
|
||||
"obfs": "tls1.2_ticket_auth_compatible",
|
||||
"obfs_param": "",
|
||||
"speed_limit_per_con": 0,
|
||||
"speed_limit_per_user": 0,
|
||||
|
||||
"additional_ports" : {}, // only works under multi-user mode
|
||||
"timeout": 120,
|
||||
"udp_timeout": 60,
|
||||
"dns_ipv6": false,
|
||||
"connect_verbose_info": 0,
|
||||
"redirect": "",
|
||||
|
||||
+77
-29
@@ -24,6 +24,7 @@ class TransferBase(object):
|
||||
self.port_uid_table = {} #端口到uid的映射(仅v3以上有用)
|
||||
self.onlineuser_cache = lru_cache.LRUCache(timeout=60*30) #用户在线状态记录
|
||||
self.pull_ok = False #记录是否已经拉出过数据
|
||||
self.mu_ports = {}
|
||||
|
||||
def load_cfg(self):
|
||||
pass
|
||||
@@ -41,7 +42,7 @@ class TransferBase(object):
|
||||
dt_transfer[id] = [self.last_get_transfer[id][0] - last_transfer[id][0], self.last_get_transfer[id][1] - last_transfer[id][1]]
|
||||
|
||||
for id in curr_transfer.keys():
|
||||
if id in self.force_update_transfer:
|
||||
if id in self.force_update_transfer or id in self.mu_ports:
|
||||
continue
|
||||
#算出与上次记录的流量差值,保存于dt_transfer表
|
||||
if id in last_transfer:
|
||||
@@ -85,6 +86,8 @@ class TransferBase(object):
|
||||
logging.error('load switchrule.py fail')
|
||||
cur_servers = {}
|
||||
new_servers = {}
|
||||
allow_users = {}
|
||||
mu_servers = {}
|
||||
for row in rows:
|
||||
try:
|
||||
allow = switchrule.isTurnOn(row) and row['enable'] == 1 and row['u'] + row['d'] < row['transfer_enable']
|
||||
@@ -93,11 +96,13 @@ class TransferBase(object):
|
||||
|
||||
port = row['port']
|
||||
passwd = common.to_bytes(row['passwd'])
|
||||
if hasattr(passwd, 'encode'):
|
||||
passwd = passwd.encode('utf-8')
|
||||
cfg = {'password': passwd}
|
||||
if 'id' in row:
|
||||
self.port_uid_table[row['port']] = row['id']
|
||||
|
||||
read_config_keys = ['method', 'obfs', 'obfs_param', 'protocol', 'protocol_param', 'forbidden_ip', 'forbidden_port']
|
||||
read_config_keys = ['method', 'obfs', 'obfs_param', 'protocol', 'protocol_param', 'forbidden_ip', 'forbidden_port', 'speed_limit_per_con', 'speed_limit_per_user']
|
||||
for name in read_config_keys:
|
||||
if name in row and row[name]:
|
||||
cfg[name] = row[name]
|
||||
@@ -113,34 +118,51 @@ class TransferBase(object):
|
||||
logging.error('more than one user use the same port [%s]' % (port,))
|
||||
continue
|
||||
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
if not allow:
|
||||
logging.info('db stop server at port [%s]' % (port,))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
else:
|
||||
cfgchange = False
|
||||
if port in ServerPool.get_instance().tcp_servers_pool:
|
||||
relay = ServerPool.get_instance().tcp_servers_pool[port]
|
||||
for name in merge_config_keys:
|
||||
if name in cfg and not self.cmp(cfg[name], relay._config[name]):
|
||||
cfgchange = True
|
||||
break;
|
||||
if not cfgchange and port in ServerPool.get_instance().tcp_ipv6_servers_pool:
|
||||
relay = ServerPool.get_instance().tcp_ipv6_servers_pool[port]
|
||||
for name in merge_config_keys:
|
||||
if name in cfg and not self.cmp(cfg[name], relay._config[name]):
|
||||
cfgchange = True
|
||||
break;
|
||||
#config changed
|
||||
if allow:
|
||||
allow_users[port] = passwd
|
||||
if 'protocol' in cfg and 'protocol_param' in cfg and common.to_str(cfg['protocol']) in ['auth_aes128_md5', 'auth_aes128_sha1']:
|
||||
if '#' in common.to_str(cfg['protocol_param']):
|
||||
mu_servers[port] = passwd
|
||||
del allow_users[port]
|
||||
|
||||
cfgchange = False
|
||||
if port in ServerPool.get_instance().tcp_servers_pool:
|
||||
relay = ServerPool.get_instance().tcp_servers_pool[port]
|
||||
for name in merge_config_keys:
|
||||
if name in cfg and not self.cmp(cfg[name], relay._config[name]):
|
||||
cfgchange = True
|
||||
break
|
||||
if not cfgchange and port in ServerPool.get_instance().tcp_ipv6_servers_pool:
|
||||
relay = ServerPool.get_instance().tcp_ipv6_servers_pool[port]
|
||||
for name in merge_config_keys:
|
||||
if name in cfg and not self.cmp(cfg[name], relay._config[name]):
|
||||
cfgchange = True
|
||||
break
|
||||
|
||||
if port in mu_servers:
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
if cfgchange:
|
||||
logging.info('db stop server at port [%s] reason: config changed: %s' % (port, cfg))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
new_servers[port] = (passwd, cfg)
|
||||
else:
|
||||
self.new_server(port, passwd, cfg)
|
||||
else:
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
if not allow:
|
||||
logging.info('db stop server at port [%s]' % (port,))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
else:
|
||||
if cfgchange:
|
||||
logging.info('db stop server at port [%s] reason: config changed: %s' % (port, cfg))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
new_servers[port] = (passwd, cfg)
|
||||
|
||||
elif allow and ServerPool.get_instance().server_run_status(port) is False:
|
||||
self.new_server(port, passwd, cfg)
|
||||
elif allow and port > 0 and port < 65536 and ServerPool.get_instance().server_run_status(port) is False:
|
||||
self.new_server(port, passwd, cfg)
|
||||
|
||||
for row in last_rows:
|
||||
if row['port'] in cur_servers:
|
||||
@@ -159,6 +181,12 @@ class TransferBase(object):
|
||||
passwd, cfg = new_servers[port]
|
||||
self.new_server(port, passwd, cfg)
|
||||
|
||||
logging.debug('db allow users %s \nmu_servers %s' % (allow_users, mu_servers))
|
||||
for port in mu_servers:
|
||||
ServerPool.get_instance().update_mu_users(port, allow_users)
|
||||
|
||||
self.mu_ports = mu_servers
|
||||
|
||||
def clear_cache(self, port):
|
||||
if port in self.force_update_transfer: del self.force_update_transfer[port]
|
||||
if port in self.last_get_transfer: del self.last_get_transfer[port]
|
||||
@@ -198,8 +226,13 @@ class TransferBase(object):
|
||||
db_instance = obj()
|
||||
ServerPool.get_instance()
|
||||
shell.log_shadowsocks_version()
|
||||
import resource
|
||||
logging.info('current process RLIMIT_NOFILE resource: soft %d hard %d' % resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||
|
||||
try:
|
||||
import resource
|
||||
logging.info('current process RLIMIT_NOFILE resource: soft %d hard %d' % resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
while True:
|
||||
load_config()
|
||||
@@ -209,6 +242,17 @@ class TransferBase(object):
|
||||
rows = db_instance.pull_db_all_user()
|
||||
if rows:
|
||||
db_instance.pull_ok = True
|
||||
config = shell.get_config(False)
|
||||
for port in config['additional_ports']:
|
||||
val = config['additional_ports'][port]
|
||||
val['port'] = int(port)
|
||||
val['enable'] = 1
|
||||
val['transfer_enable'] = 1024 ** 7
|
||||
val['u'] = 0
|
||||
val['d'] = 0
|
||||
if "password" in val:
|
||||
val["passwd"] = val["password"]
|
||||
rows.append(val)
|
||||
db_instance.del_server_out_of_bound_safe(last_rows, rows)
|
||||
last_rows = rows
|
||||
except Exception as e:
|
||||
@@ -250,7 +294,7 @@ class DbTransfer(TransferBase):
|
||||
import json
|
||||
config_path = get_config().MYSQL_CONFIG
|
||||
cfg = None
|
||||
with open(config_path, 'r+') as f:
|
||||
with open(config_path, 'rb+') as f:
|
||||
cfg = json.loads(f.read().decode('utf8'))
|
||||
|
||||
if cfg:
|
||||
@@ -363,8 +407,12 @@ class Dbv3Transfer(DbTransfer):
|
||||
def __init__(self):
|
||||
super(Dbv3Transfer, self).__init__()
|
||||
self.key_list += ['id', 'method']
|
||||
self.ss_node_info_name = 'ss_node_info_log'
|
||||
if get_config().API_INTERFACE == 'sspanelv3ssr':
|
||||
self.key_list += ['obfs', 'protocol']
|
||||
if get_config().API_INTERFACE == 'glzjinmod':
|
||||
self.key_list += ['obfs', 'protocol']
|
||||
self.ss_node_info_name = 'ss_node_info'
|
||||
self.start_time = time.time()
|
||||
|
||||
def update_all_user(self, dt_transfer):
|
||||
@@ -445,14 +493,14 @@ class Dbv3Transfer(DbTransfer):
|
||||
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("INSERT INTO `ss_node_info_log` (`id`, `node_id`, `uptime`, `load`, `log_time`) VALUES (NULL, '" + \
|
||||
cur.execute("INSERT INTO `" + self.ss_node_info_name + "` (`id`, `node_id`, `uptime`, `load`, `log_time`) VALUES (NULL, '" + \
|
||||
str(self.cfg["node_id"]) + "', '" + str(self.uptime()) + "', '" + \
|
||||
str(self.load()) + "', unix_timestamp()); ")
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
cur.close()
|
||||
except:
|
||||
logging.warn('no `ss_node_online_log` or `ss_node_info_log` in db')
|
||||
logging.warn('no `ss_node_online_log` or `" + self.ss_node_info_name + "` in db')
|
||||
|
||||
conn.close()
|
||||
return update_transfer
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
If Not Exist "userapiconfig.py" Copy "apiconfig.py" "userapiconfig.py"
|
||||
If Not Exist "user-config.json" Copy "config.json" "user-config.json"
|
||||
If Not Exist "usermysql.json" Copy "mysql.json" "usermysql.json"
|
||||
+94
-50
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import traceback
|
||||
@@ -10,21 +10,30 @@ import sys
|
||||
import json
|
||||
import base64
|
||||
|
||||
|
||||
class MuJsonLoader(object):
|
||||
def __init__(self):
|
||||
self.json = None
|
||||
|
||||
def load(self, path):
|
||||
with open(path, 'rb+') as f:
|
||||
self.json = json.loads(f.read().decode('utf8'))
|
||||
l = "[]"
|
||||
try:
|
||||
with open(path, 'rb+') as f:
|
||||
l = f.read().decode('utf8')
|
||||
except:
|
||||
pass
|
||||
self.json = json.loads(l)
|
||||
|
||||
def save(self, path):
|
||||
if self.json:
|
||||
if self.json is not None:
|
||||
output = json.dumps(self.json, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
with open(path, 'r+') as f:
|
||||
f.write(output)
|
||||
with open(path, 'a'):
|
||||
pass
|
||||
with open(path, 'rb+') as f:
|
||||
f.write(output.encode('utf8'))
|
||||
f.truncate()
|
||||
|
||||
|
||||
class MuMgr(object):
|
||||
def __init__(self):
|
||||
self.config_path = get_config().MUDB_FILE
|
||||
@@ -37,10 +46,9 @@ class MuMgr(object):
|
||||
if self.server_addr == '127.0.0.1':
|
||||
self.server_addr = self.getipaddr()
|
||||
|
||||
def getipaddr(self, ifname = 'eth0'):
|
||||
def getipaddr(self, ifname='eth0'):
|
||||
import socket
|
||||
import struct
|
||||
import fcntl
|
||||
ret = '127.0.0.1'
|
||||
try:
|
||||
ret = socket.gethostbyname(socket.getfqdn(socket.gethostname()))
|
||||
@@ -48,53 +56,79 @@ class MuMgr(object):
|
||||
pass
|
||||
if ret == '127.0.0.1':
|
||||
try:
|
||||
import fcntl
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
ret = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
|
||||
except:
|
||||
pass
|
||||
return ret
|
||||
|
||||
def ssrlink(self, user, encode):
|
||||
def ssrlink(self, user, encode, muid):
|
||||
protocol = user.get('protocol', '')
|
||||
obfs = user.get('obfs', '')
|
||||
protocol = protocol.replace("_compatible", "")
|
||||
obfs = obfs.replace("_compatible", "")
|
||||
link = "%s:%s:%s:%s:%s:%s" % (self.server_addr, user['port'], protocol, user['method'], obfs, common.to_str(base64.urlsafe_b64encode(common.to_bytes(user['passwd']))).replace("=", ""))
|
||||
return "ssr://" + ( encode and common.to_str(base64.urlsafe_b64encode(common.to_bytes(link))).replace("=", "") or link)
|
||||
protocol_param = ''
|
||||
if muid is not None:
|
||||
protocol_param_ = user.get('protocol_param', '')
|
||||
param = protocol_param_.split('#')
|
||||
if len(param) == 2:
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
param = str(muid) + ':' + row['passwd']
|
||||
protocol_param = '/?protoparam=' + common.to_str(base64.urlsafe_b64encode(common.to_bytes(param))).replace("=", "")
|
||||
break
|
||||
link = ("%s:%s:%s:%s:%s:%s" % (self.server_addr, user['port'], protocol, user['method'], obfs, common.to_str(base64.urlsafe_b64encode(common.to_bytes(user['passwd']))).replace("=", ""))) + protocol_param
|
||||
return "ssr://" + (encode and common.to_str(base64.urlsafe_b64encode(common.to_bytes(link))).replace("=", "") or link)
|
||||
|
||||
def userinfo(self, user):
|
||||
def userinfo(self, user, muid = None):
|
||||
ret = ""
|
||||
key_list = ['user', 'port', 'method', 'passwd', 'protocol', 'protocol_param', 'obfs', 'obfs_param', 'transfer_enable', 'u', 'd']
|
||||
for key in sorted(user):
|
||||
if key in ['enable']:
|
||||
if key not in key_list:
|
||||
key_list.append(key)
|
||||
for key in key_list:
|
||||
if key in ['enable'] or key not in user:
|
||||
continue
|
||||
ret += '\n'
|
||||
if key in ['transfer_enable', 'u', 'd'] :
|
||||
val = user[key]
|
||||
if (muid is not None) and (key in ['protocol_param']):
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
ret += " %s : %s" % (key, str(muid) + ':' + row['passwd'])
|
||||
break
|
||||
elif key in ['transfer_enable', 'u', 'd']:
|
||||
if muid is not None:
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
val = row[key]
|
||||
break
|
||||
else:
|
||||
val = user[key]
|
||||
if val / 1024 < 4:
|
||||
ret += " %s : %s" % (key, val)
|
||||
elif val / 1024**2 < 4:
|
||||
elif val / 1024 ** 2 < 4:
|
||||
val /= float(1024)
|
||||
ret += " %s : %s K Bytes" % (key, val)
|
||||
elif val / 1024**3 < 4:
|
||||
val /= float(1024**2)
|
||||
elif val / 1024 ** 3 < 4:
|
||||
val /= float(1024 ** 2)
|
||||
ret += " %s : %s M Bytes" % (key, val)
|
||||
else:
|
||||
val /= float(1024**3)
|
||||
val /= float(1024 ** 3)
|
||||
ret += " %s : %s G Bytes" % (key, val)
|
||||
else:
|
||||
ret += " %s : %s" % (key, user[key])
|
||||
ret += "\n " + self.ssrlink(user, False)
|
||||
ret += "\n " + self.ssrlink(user, True)
|
||||
ret += "\n " + self.ssrlink(user, False, muid)
|
||||
ret += "\n " + self.ssrlink(user, True, muid)
|
||||
return ret
|
||||
|
||||
def rand_pass(self):
|
||||
return ''.join([random.choice('''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~-_=+(){}[]^&%$@''') for i in range(8)])
|
||||
|
||||
def add(self, user):
|
||||
up = {'enable': 1, 'u': 0, 'd': 0, 'method': "aes-128-cfb",
|
||||
'protocol': "auth_sha1_v4",
|
||||
up = {'enable': 1, 'u': 0, 'd': 0, 'method': "aes-128-ctr",
|
||||
'protocol': "auth_aes128_md5",
|
||||
'obfs': "tls1.2_ticket_auth_compatible",
|
||||
'transfer_enable': 1125899906842624}
|
||||
'transfer_enable': 9007199254740992}
|
||||
up['passwd'] = self.rand_pass()
|
||||
up.update(user)
|
||||
|
||||
@@ -170,36 +204,42 @@ class MuMgr(object):
|
||||
if 'port' in user and row['port'] != user['port']:
|
||||
match = False
|
||||
if match:
|
||||
print("### user [%s] info %s" % (row['user'], self.userinfo(row)))
|
||||
muid = None
|
||||
if 'muid' in user:
|
||||
muid = user['muid']
|
||||
print("### user [%s] info %s" % (row['user'], self.userinfo(row, muid)))
|
||||
|
||||
|
||||
def print_server_help():
|
||||
print('''usage: python mujson_manage.py -a|-d|-e|-c|-l [OPTION]...
|
||||
print('''usage: python mujson_manage.py -a|-d|-e|-c|-l [OPTION]...
|
||||
|
||||
Actions:
|
||||
-a ADD add/edit a user
|
||||
-d DELETE delete a user
|
||||
-e EDIT edit a user
|
||||
-c CLEAR set u/d to zero
|
||||
-l LIST display a user infomation or all users infomation
|
||||
-a ADD add/edit a user
|
||||
-d DELETE delete a user
|
||||
-e EDIT edit a user
|
||||
-c CLEAR set u/d to zero
|
||||
-l LIST display a user infomation or all users infomation
|
||||
|
||||
Options:
|
||||
-u USER the user name
|
||||
-p PORT server port
|
||||
-k PASSWORD password
|
||||
-m METHOD encryption method, default: aes-128-cfb
|
||||
-O PROTOCOL protocol plugin, default: auth_sha1_v4
|
||||
-o OBFS obfs plugin, default: tls1.2_ticket_auth_compatible
|
||||
-G PROTOCOL_PARAM protocol plugin param
|
||||
-g OBFS_PARAM obfs plugin param
|
||||
-t TRANSFER max transfer for G bytes, default: 1048576, can be float point number
|
||||
-f FORBID set forbidden ports. Example (ban 1~79 and 81~100): -f "1-79,81-100"
|
||||
-u USER the user name
|
||||
-p PORT server port (only this option must be set if add a user)
|
||||
-k PASSWORD password
|
||||
-m METHOD encryption method, default: aes-128-ctr
|
||||
-O PROTOCOL protocol plugin, default: auth_aes128_md5
|
||||
-o OBFS obfs plugin, default: tls1.2_ticket_auth_compatible
|
||||
-G PROTOCOL_PARAM protocol plugin param
|
||||
-g OBFS_PARAM obfs plugin param
|
||||
-t TRANSFER max transfer for G bytes, default: 8388608 (8 PB or 8192 TB)
|
||||
-f FORBID set forbidden ports. Example (ban 1~79 and 81~100): -f "1-79,81-100"
|
||||
-i MUID set sub id to display (only work with -l)
|
||||
|
||||
General options:
|
||||
-h, --help show this help message and exit
|
||||
-h, --help show this help message and exit
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
shortopts = 'adeclu:p:k:O:o:G:g:m:t:f:h'
|
||||
shortopts = 'adeclu:i:p:k:O:o:G:g:m:t:f:h'
|
||||
longopts = ['help']
|
||||
action = None
|
||||
user = {}
|
||||
@@ -209,11 +249,14 @@ def main():
|
||||
'+2': 'tls1.2_ticket_auth_compatible',
|
||||
'2': 'tls1.2_ticket_auth'}
|
||||
fast_set_protocol = {'0': 'origin',
|
||||
'+1': 'verify_sha1_compatible',
|
||||
'1': 'verify_sha1',
|
||||
'2': 'auth_sha1',
|
||||
'3': 'auth_sha1_v2',
|
||||
'4': 'auth_sha1_v4',
|
||||
'+ota': 'verify_sha1_compatible',
|
||||
'ota': 'verify_sha1',
|
||||
'a1': 'auth_sha1',
|
||||
'+a1': 'auth_sha1_compatible',
|
||||
'a2': 'auth_sha1_v2',
|
||||
'+a2': 'auth_sha1_v2_compatible',
|
||||
'a4': 'auth_sha1_v4',
|
||||
'+a4': 'auth_sha1_v4_compatible',
|
||||
'am': 'auth_aes128_md5',
|
||||
'as': 'auth_aes128_sha1',
|
||||
}
|
||||
@@ -247,6 +290,8 @@ def main():
|
||||
action = 0
|
||||
elif key == '-u':
|
||||
user['user'] = value
|
||||
elif key == '-i':
|
||||
user['muid'] = int(value)
|
||||
elif key == '-p':
|
||||
user['port'] = int(value)
|
||||
elif key == '-k':
|
||||
@@ -278,7 +323,7 @@ def main():
|
||||
val = int(value)
|
||||
except:
|
||||
pass
|
||||
user['transfer_enable'] = val * (1024 ** 3)
|
||||
user['transfer_enable'] = int(val * 1024) * (1024 ** 2)
|
||||
elif key in ('-h', '--help'):
|
||||
print_server_help()
|
||||
sys.exit(0)
|
||||
@@ -313,4 +358,3 @@ def main():
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ from configloader import load_config, get_config
|
||||
|
||||
class MainThread(threading.Thread):
|
||||
def __init__(self, obj):
|
||||
threading.Thread.__init__(self)
|
||||
super(MainThread, self).__init__()
|
||||
self.daemon = True
|
||||
self.obj = obj
|
||||
|
||||
def run(self):
|
||||
|
||||
+61
-9
@@ -23,6 +23,7 @@
|
||||
|
||||
import os
|
||||
import logging
|
||||
import struct
|
||||
import time
|
||||
from shadowsocks import shell, eventloop, tcprelay, udprelay, asyncdns, common
|
||||
import threading
|
||||
@@ -32,7 +33,7 @@ from configloader import load_config, get_config
|
||||
|
||||
class MainThread(threading.Thread):
|
||||
def __init__(self, params):
|
||||
threading.Thread.__init__(self)
|
||||
super(MainThread, self).__init__()
|
||||
self.params = params
|
||||
|
||||
def run(self):
|
||||
@@ -213,23 +214,62 @@ class ServerPool(object):
|
||||
|
||||
return True
|
||||
|
||||
def update_mu_users(self, port, users):
|
||||
port = int(port)
|
||||
if port in self.tcp_servers_pool:
|
||||
try:
|
||||
self.tcp_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
try:
|
||||
self.udp_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
try:
|
||||
self.tcp_ipv6_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
try:
|
||||
self.udp_ipv6_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
|
||||
def get_server_transfer(self, port):
|
||||
port = int(port)
|
||||
uid = struct.pack('<I', port)
|
||||
ret = [0, 0]
|
||||
if port in self.tcp_servers_pool:
|
||||
ret[0] = self.tcp_servers_pool[port].server_transfer_ul
|
||||
ret[1] = self.tcp_servers_pool[port].server_transfer_dl
|
||||
ret[0], ret[1] = self.tcp_servers_pool[port].get_ud()
|
||||
if port in self.udp_servers_pool:
|
||||
ret[0] += self.udp_servers_pool[port].server_transfer_ul
|
||||
ret[1] += self.udp_servers_pool[port].server_transfer_dl
|
||||
u, d = self.udp_servers_pool[port].get_ud()
|
||||
ret[0] += u
|
||||
ret[1] += d
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
ret[0] += self.tcp_ipv6_servers_pool[port].server_transfer_ul
|
||||
ret[1] += self.tcp_ipv6_servers_pool[port].server_transfer_dl
|
||||
u, d = self.tcp_ipv6_servers_pool[port].get_ud()
|
||||
ret[0] += u
|
||||
ret[1] += d
|
||||
if port in self.udp_ipv6_servers_pool:
|
||||
ret[0] += self.udp_ipv6_servers_pool[port].server_transfer_ul
|
||||
ret[1] += self.udp_ipv6_servers_pool[port].server_transfer_dl
|
||||
u, d = self.udp_ipv6_servers_pool[port].get_ud()
|
||||
ret[0] += u
|
||||
ret[1] += d
|
||||
return ret
|
||||
|
||||
def get_server_mu_transfer(self, server):
|
||||
return server.get_users_ud()
|
||||
|
||||
def update_mu_transfer(self, user_dict, u, d):
|
||||
for uid in u:
|
||||
port = struct.unpack('<I', uid)[0]
|
||||
if port not in user_dict:
|
||||
user_dict[port] = [0, 0]
|
||||
user_dict[port][0] += u[uid]
|
||||
for uid in d:
|
||||
port = struct.unpack('<I', uid)[0]
|
||||
if port not in user_dict:
|
||||
user_dict[port] = [0, 0]
|
||||
user_dict[port][1] += d[uid]
|
||||
|
||||
def get_servers_transfer(self):
|
||||
servers = self.tcp_servers_pool.copy()
|
||||
servers.update(self.tcp_ipv6_servers_pool)
|
||||
@@ -238,5 +278,17 @@ class ServerPool(object):
|
||||
ret = {}
|
||||
for port in servers.keys():
|
||||
ret[port] = self.get_server_transfer(port)
|
||||
for port in self.tcp_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.tcp_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
for port in self.tcp_ipv6_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.tcp_ipv6_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
for port in self.udp_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.udp_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
for port in self.udp_ipv6_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.udp_ipv6_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
return ret
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ EVENT_NAMES = {
|
||||
}
|
||||
|
||||
# we check timeouts every TIMEOUT_PRECISION seconds
|
||||
TIMEOUT_PRECISION = 5
|
||||
TIMEOUT_PRECISION = 2
|
||||
|
||||
|
||||
class KqueueLoop(object):
|
||||
@@ -208,12 +208,13 @@ class EventLoop(object):
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
handle = False
|
||||
for sock, fd, event in events:
|
||||
handler = self._fdmap.get(fd, None)
|
||||
if handler is not None:
|
||||
handler = handler[1]
|
||||
try:
|
||||
handler.handle_event(sock, fd, event)
|
||||
handle = handle or handler.handle_event(sock, fd, event)
|
||||
except (OSError, IOError) as e:
|
||||
shell.print_exception(e)
|
||||
now = time.time()
|
||||
@@ -221,6 +222,8 @@ class EventLoop(object):
|
||||
for callback in self._periodic_callbacks:
|
||||
callback()
|
||||
self._last_time = now
|
||||
if events and not handle:
|
||||
time.sleep(0.001)
|
||||
|
||||
def __del__(self):
|
||||
self._impl.close()
|
||||
|
||||
@@ -88,11 +88,11 @@ class LRUCache(collections.MutableMapping):
|
||||
for key in self._keys_to_last_time:
|
||||
return key
|
||||
|
||||
def sweep(self):
|
||||
def sweep(self, sweep_item_cnt = SWEEP_MAX_ITEMS):
|
||||
# O(n - m)
|
||||
now = time.time()
|
||||
c = 0
|
||||
while c < SWEEP_MAX_ITEMS:
|
||||
while c < sweep_item_cnt:
|
||||
if len(self._keys_to_last_time) == 0:
|
||||
break
|
||||
for key in self._keys_to_last_time:
|
||||
|
||||
@@ -53,6 +53,9 @@ class obfs(object):
|
||||
def set_server_info(self, server_info):
|
||||
return self.obfs.set_server_info(server_info)
|
||||
|
||||
def get_server_info(self):
|
||||
return self.obfs.get_server_info()
|
||||
|
||||
def get_method_info(self, method):
|
||||
method = method.lower()
|
||||
m = method_supported.get(method)
|
||||
@@ -62,6 +65,9 @@ class obfs(object):
|
||||
m = self._method_info
|
||||
return m[0](method)
|
||||
|
||||
def get_overhead(self, direction):
|
||||
return self.obfs.get_overhead(direction)
|
||||
|
||||
def client_pre_encrypt(self, buf):
|
||||
return self.obfs.client_pre_encrypt(buf)
|
||||
|
||||
|
||||
Regular → Executable
+157
-44
@@ -26,6 +26,7 @@ import base64
|
||||
import time
|
||||
import datetime
|
||||
import random
|
||||
import math
|
||||
import struct
|
||||
import zlib
|
||||
import hmac
|
||||
@@ -81,10 +82,14 @@ class auth_base(plain.plain):
|
||||
super(auth_base, self).__init__(method)
|
||||
self.method = method
|
||||
self.no_compatible_method = ''
|
||||
self.overhead = 7
|
||||
|
||||
def init_data(self):
|
||||
return ''
|
||||
|
||||
def get_overhead(self, direction): # direction: true for c->s false for s->c
|
||||
return self.overhead
|
||||
|
||||
def set_server_info(self, server_info):
|
||||
self.server_info = server_info
|
||||
|
||||
@@ -102,6 +107,7 @@ class auth_base(plain.plain):
|
||||
|
||||
def not_match_return(self, buf):
|
||||
self.raw_trans = True
|
||||
self.overhead = 0
|
||||
if self.method == self.no_compatible_method:
|
||||
return (b'E'*2048, False)
|
||||
return (buf, False)
|
||||
@@ -870,6 +876,9 @@ class auth_aes128(auth_base):
|
||||
def init_data(self):
|
||||
return obfs_auth_v2_data()
|
||||
|
||||
def get_overhead(self, direction): # direction: true for c->s false for s->c
|
||||
return 9
|
||||
|
||||
def set_server_info(self, server_info):
|
||||
self.server_info = server_info
|
||||
try:
|
||||
@@ -1103,8 +1112,55 @@ class auth_aes128(auth_base):
|
||||
length = len(buf)
|
||||
data = buf[:-4]
|
||||
if struct.pack('<I', zlib.adler32(data) & 0xFFFFFFFF) != buf[length - 4:]:
|
||||
return b''
|
||||
return data
|
||||
return (b'', None)
|
||||
return (data, None)
|
||||
|
||||
class obfs_auth_mu_data(object):
|
||||
def __init__(self):
|
||||
self.user_id = {}
|
||||
self.local_client_id = b''
|
||||
self.connection_id = 0
|
||||
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:
|
||||
self.user_id[user_id] = lru_cache.LRUCache()
|
||||
local_client_id = self.user_id[user_id]
|
||||
|
||||
if client_id in local_client_id:
|
||||
local_client_id[client_id].update()
|
||||
|
||||
def set_max_client(self, max_client):
|
||||
self.max_client = max_client
|
||||
self.max_buffer = max(self.max_client * 2, 1024)
|
||||
|
||||
def insert(self, user_id, client_id, connection_id):
|
||||
if user_id not in self.user_id:
|
||||
self.user_id[user_id] = lru_cache.LRUCache()
|
||||
local_client_id = self.user_id[user_id]
|
||||
|
||||
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
|
||||
local_client_id[client_id] = client_queue(connection_id)
|
||||
else:
|
||||
local_client_id[client_id].re_enable(connection_id)
|
||||
return local_client_id[client_id].insert(connection_id)
|
||||
|
||||
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
|
||||
local_client_id[client_id] = client_queue(connection_id)
|
||||
else:
|
||||
local_client_id[client_id].re_enable(connection_id)
|
||||
return local_client_id[client_id].insert(connection_id)
|
||||
|
||||
logging.warn('auth_aes128: no inactive client')
|
||||
return False
|
||||
else:
|
||||
return local_client_id[client_id].insert(connection_id)
|
||||
|
||||
class auth_aes128_sha1(auth_base):
|
||||
def __init__(self, method, hashfunc):
|
||||
@@ -1123,37 +1179,60 @@ class auth_aes128_sha1(auth_base):
|
||||
self.extra_wait_size = struct.unpack('>H', os.urandom(2))[0] % 1024
|
||||
self.pack_id = 1
|
||||
self.recv_id = 1
|
||||
self.user_id = None
|
||||
self.user_key = None
|
||||
self.last_rnd_len = 0
|
||||
self.overhead = 9
|
||||
|
||||
def init_data(self):
|
||||
return obfs_auth_v2_data()
|
||||
return obfs_auth_mu_data()
|
||||
|
||||
def get_overhead(self, direction): # direction: true for c->s false for s->c
|
||||
return self.overhead
|
||||
|
||||
def set_server_info(self, server_info):
|
||||
self.server_info = server_info
|
||||
try:
|
||||
max_client = int(server_info.protocol_param)
|
||||
max_client = int(server_info.protocol_param.split('#')[0])
|
||||
except:
|
||||
max_client = 64
|
||||
self.server_info.data.set_max_client(max_client)
|
||||
|
||||
def rnd_data(self, buf_size):
|
||||
if buf_size > 1200:
|
||||
return b'\x01'
|
||||
def trapezoid_random_float(self, d):
|
||||
if d == 0:
|
||||
return random.random()
|
||||
s = random.random()
|
||||
a = 1 - d
|
||||
return (math.sqrt(a * a + 4 * d * s) - a) / (2 * d)
|
||||
|
||||
if self.pack_id > 4:
|
||||
rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 32)
|
||||
elif buf_size > 900:
|
||||
rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 128)
|
||||
else:
|
||||
rnd_data = os.urandom(struct.unpack('>H', os.urandom(2))[0] % 512)
|
||||
def trapezoid_random_int(self, max_val, d):
|
||||
v = self.trapezoid_random_float(d)
|
||||
return int(v * max_val)
|
||||
|
||||
if len(rnd_data) < 128:
|
||||
return common.chr(len(rnd_data) + 1) + rnd_data
|
||||
else:
|
||||
return common.chr(255) + struct.pack('<H', len(rnd_data) + 3) + rnd_data
|
||||
def rnd_data_len(self, buf_size, full_buf_size):
|
||||
if full_buf_size >= self.server_info.buffer_size:
|
||||
return 0
|
||||
rev_len = self.server_info.tcp_mss - buf_size - 9
|
||||
if rev_len == 0:
|
||||
return 0
|
||||
if rev_len < 0:
|
||||
if rev_len > -self.server_info.tcp_mss:
|
||||
return self.trapezoid_random_int(rev_len + self.server_info.tcp_mss, -0.3)
|
||||
return common.ord(os.urandom(1)[0]) % 32
|
||||
if buf_size > 900:
|
||||
return struct.unpack('>H', os.urandom(2))[0] % rev_len
|
||||
return self.trapezoid_random_int(rev_len, -0.3)
|
||||
|
||||
def pack_data(self, buf):
|
||||
data = self.rnd_data(len(buf)) + buf
|
||||
def rnd_data(self, buf_size, full_buf_size):
|
||||
data_len = self.rnd_data_len(buf_size, full_buf_size)
|
||||
|
||||
if data_len < 128:
|
||||
return common.chr(data_len + 1) + os.urandom(data_len)
|
||||
|
||||
return common.chr(255) + struct.pack('<H', data_len + 1) + os.urandom(data_len - 2)
|
||||
|
||||
def pack_data(self, buf, full_buf_size):
|
||||
data = self.rnd_data(len(buf), full_buf_size) + buf
|
||||
data_len = len(data) + 8
|
||||
mac_key = self.user_key + struct.pack('<I', self.pack_id)
|
||||
mac = hmac.new(mac_key, struct.pack('<H', data_len), self.hashfunc).digest()[:2]
|
||||
@@ -1174,9 +1253,16 @@ class auth_aes128_sha1(auth_base):
|
||||
data = data + struct.pack('<H', data_len) + struct.pack('<H', rnd_len)
|
||||
mac_key = self.server_info.iv + self.server_info.key
|
||||
uid = os.urandom(4)
|
||||
#if self.user_key: uid = self.uid else:
|
||||
self.user_key = self.server_info.key
|
||||
encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.server_info.key)) + self.salt, 'aes-128-cbc', b'\x00' * 16)
|
||||
if b':' in to_bytes(self.server_info.protocol_param):
|
||||
try:
|
||||
items = to_bytes(self.server_info.protocol_param).split(b':')
|
||||
self.user_key = self.hashfunc(items[1]).digest()
|
||||
uid = struct.pack('<I', int(items[0]))
|
||||
except:
|
||||
pass
|
||||
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)
|
||||
data = uid + encryptor.encrypt(data)[16:]
|
||||
data += hmac.new(mac_key, data, self.hashfunc).digest()[:4]
|
||||
check_head = os.urandom(1)
|
||||
@@ -1200,6 +1286,7 @@ class auth_aes128_sha1(auth_base):
|
||||
|
||||
def client_pre_encrypt(self, buf):
|
||||
ret = b''
|
||||
ogn_data_len = len(buf)
|
||||
if not self.has_sent_header:
|
||||
head_size = self.get_head_size(buf, 30)
|
||||
datalen = min(len(buf), random.randint(0, 31) + head_size)
|
||||
@@ -1207,9 +1294,10 @@ class auth_aes128_sha1(auth_base):
|
||||
buf = buf[datalen:]
|
||||
self.has_sent_header = True
|
||||
while len(buf) > self.unit_len:
|
||||
ret += self.pack_data(buf[:self.unit_len])
|
||||
ret += self.pack_data(buf[:self.unit_len], ogn_data_len)
|
||||
buf = buf[self.unit_len:]
|
||||
ret += self.pack_data(buf)
|
||||
ret += self.pack_data(buf, ogn_data_len)
|
||||
self.last_rnd_len = ogn_data_len
|
||||
return ret
|
||||
|
||||
def client_post_decrypt(self, buf):
|
||||
@@ -1250,10 +1338,12 @@ class auth_aes128_sha1(auth_base):
|
||||
if self.raw_trans:
|
||||
return buf
|
||||
ret = b''
|
||||
ogn_data_len = len(buf)
|
||||
while len(buf) > self.unit_len:
|
||||
ret += self.pack_data(buf[:self.unit_len])
|
||||
ret += self.pack_data(buf[:self.unit_len], ogn_data_len)
|
||||
buf = buf[self.unit_len:]
|
||||
ret += self.pack_data(buf)
|
||||
ret += self.pack_data(buf, ogn_data_len)
|
||||
self.last_rnd_len = ogn_data_len
|
||||
return ret
|
||||
|
||||
def server_post_decrypt(self, buf):
|
||||
@@ -1264,12 +1354,12 @@ class auth_aes128_sha1(auth_base):
|
||||
sendback = False
|
||||
|
||||
if not self.has_recv_header:
|
||||
if len(self.recv_buf) < 7:
|
||||
return (b'', False)
|
||||
mac_key = self.server_info.recv_iv + self.server_info.key
|
||||
sha1data = hmac.new(mac_key, self.recv_buf[:1], self.hashfunc).digest()[:6]
|
||||
if sha1data != self.recv_buf[1:7]:
|
||||
return self.not_match_return(self.recv_buf)
|
||||
if len(self.recv_buf) >= 7 or len(self.recv_buf) in [2, 3]:
|
||||
recv_len = min(len(self.recv_buf), 7)
|
||||
mac_key = self.server_info.recv_iv + self.server_info.key
|
||||
sha1data = hmac.new(mac_key, self.recv_buf[:1], self.hashfunc).digest()[:recv_len - 1]
|
||||
if sha1data != self.recv_buf[1:recv_len]:
|
||||
return self.not_match_return(self.recv_buf)
|
||||
|
||||
if len(self.recv_buf) < 31:
|
||||
return (b'', False)
|
||||
@@ -1280,9 +1370,16 @@ class auth_aes128_sha1(auth_base):
|
||||
return (b'', False)
|
||||
return self.not_match_return(self.recv_buf)
|
||||
|
||||
user_key = self.recv_buf[7:11]
|
||||
#if user_key in user_map: self.user_key[user_key] else: # TODO
|
||||
self.user_key = self.server_info.key
|
||||
uid = self.recv_buf[7:11]
|
||||
if uid in self.server_info.users:
|
||||
self.user_id = uid
|
||||
self.user_key = self.hashfunc(self.server_info.users[uid]).digest()
|
||||
self.server_info.update_user_func(uid)
|
||||
else:
|
||||
if not self.server_info.users:
|
||||
self.user_key = self.server_info.key
|
||||
else:
|
||||
self.user_key = self.server_info.recv_iv
|
||||
encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc')
|
||||
head = encryptor.decrypt(b'\x00' * 16 + self.recv_buf[11:27] + b'\x00') # need an extra byte or recv empty
|
||||
length = struct.unpack('<H', head[12:14])[0]
|
||||
@@ -1300,7 +1397,7 @@ class auth_aes128_sha1(auth_base):
|
||||
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)))
|
||||
return self.not_match_return(self.recv_buf)
|
||||
elif self.server_info.data.insert(client_id, connection_id):
|
||||
elif self.server_info.data.insert(self.user_id, client_id, connection_id):
|
||||
self.has_recv_header = True
|
||||
out_buf = self.recv_buf[31 + rnd_len:length - 4]
|
||||
self.client_id = client_id
|
||||
@@ -1356,14 +1453,23 @@ class auth_aes128_sha1(auth_base):
|
||||
sendback = True
|
||||
|
||||
if out_buf:
|
||||
self.server_info.data.update(self.client_id, self.connection_id)
|
||||
self.server_info.data.update(self.user_id, self.client_id, self.connection_id)
|
||||
return (out_buf, sendback)
|
||||
|
||||
def client_udp_pre_encrypt(self, buf):
|
||||
uid = os.urandom(4)
|
||||
user_key = self.server_info.key
|
||||
buf += uid
|
||||
return buf + hmac.new(user_key, buf, self.hashfunc).digest()[:4]
|
||||
if self.user_key is None:
|
||||
if b':' in to_bytes(self.server_info.protocol_param):
|
||||
try:
|
||||
items = to_bytes(self.server_info.protocol_param).split(':')
|
||||
self.user_key = self.hashfunc(items[1]).digest()
|
||||
self.user_id = struct.pack('<I', int(items[0]))
|
||||
except:
|
||||
pass
|
||||
if self.user_key is None:
|
||||
self.user_id = os.urandom(4)
|
||||
self.user_key = self.server_info.key
|
||||
buf += self.user_id
|
||||
return buf + hmac.new(self.user_key, buf, self.hashfunc).digest()[:4]
|
||||
|
||||
def client_udp_post_decrypt(self, buf):
|
||||
user_key = self.server_info.key
|
||||
@@ -1377,8 +1483,15 @@ class auth_aes128_sha1(auth_base):
|
||||
|
||||
def server_udp_post_decrypt(self, buf):
|
||||
uid = buf[-8:-4]
|
||||
user_key = self.server_info.key
|
||||
if uid in self.server_info.users:
|
||||
user_key = self.hashfunc(self.server_info.users[uid]).digest()
|
||||
else:
|
||||
uid = None
|
||||
if not self.server_info.users:
|
||||
user_key = self.server_info.key
|
||||
else:
|
||||
user_key = self.server_info.recv_iv
|
||||
if hmac.new(user_key, buf[:-4], self.hashfunc).digest()[:4] != buf[-4:]:
|
||||
return b''
|
||||
return buf[:-8]
|
||||
return (b'', None)
|
||||
return (buf[:-8], uid)
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ class http_simple(plain.plain):
|
||||
if lines and len(lines) > 1:
|
||||
for line in lines:
|
||||
if match_begin(line, b"Host: "):
|
||||
return line[6:]
|
||||
return common.to_str(line[6:])
|
||||
|
||||
def not_match_return(self, buf):
|
||||
self.has_sent_header = True
|
||||
@@ -168,6 +168,11 @@ class http_simple(plain.plain):
|
||||
return (b'E'*2048, False, False)
|
||||
return (buf, True, False)
|
||||
|
||||
def error_return(self, buf):
|
||||
self.has_sent_header = True
|
||||
self.has_recv_header = True
|
||||
return (b'E'*2048, False, False)
|
||||
|
||||
def server_decode(self, buf):
|
||||
if self.has_recv_header:
|
||||
return (buf, True, False)
|
||||
@@ -199,10 +204,10 @@ class http_simple(plain.plain):
|
||||
if host not in hosts:
|
||||
return self.not_match_return(buf)
|
||||
if len(ret_buf) < 4:
|
||||
return self.not_match_return(buf)
|
||||
return self.error_return(buf)
|
||||
if len(datas) > 1:
|
||||
ret_buf += datas[1]
|
||||
if len(ret_buf) >= 7:
|
||||
if len(ret_buf) >= 13:
|
||||
self.has_recv_header = True
|
||||
return (ret_buf, True, False)
|
||||
return self.not_match_return(buf)
|
||||
|
||||
@@ -54,6 +54,7 @@ class obfs_auth_data(object):
|
||||
self.client_data = lru_cache.LRUCache(60 * 5)
|
||||
self.client_id = os.urandom(32)
|
||||
self.startup_time = int(time.time() - 60 * 30) & 0xFFFFFFFF
|
||||
self.ticket_buf = {}
|
||||
|
||||
class tls_ticket_auth(plain.plain):
|
||||
def __init__(self, method):
|
||||
@@ -64,10 +65,14 @@ class tls_ticket_auth(plain.plain):
|
||||
self.client_id = b''
|
||||
self.max_time_dif = 60 * 60 * 24 # time dif (second) setting
|
||||
self.tls_version = b'\x03\x03'
|
||||
self.overhead = 5
|
||||
|
||||
def init_data(self):
|
||||
return obfs_auth_data()
|
||||
|
||||
def get_overhead(self, direction): # direction: true for c->s false for s->c
|
||||
return self.overhead
|
||||
|
||||
def sni(self, url):
|
||||
url = common.to_bytes(url)
|
||||
data = b"\x00" + struct.pack('>H', len(url)) + url
|
||||
@@ -104,7 +109,9 @@ class tls_ticket_auth(plain.plain):
|
||||
host = random.choice(hosts)
|
||||
ext += self.sni(host)
|
||||
ext += b"\x00\x17\x00\x00"
|
||||
ext += b"\x00\x23\x00\xd0" + os.urandom(208) # ticket
|
||||
if host not in self.server_info.data.ticket_buf:
|
||||
self.server_info.data.ticket_buf[host] = os.urandom((struct.unpack('>H', os.urandom(2))[0] % 17 + 8) * 16)
|
||||
ext += b"\x00\x23" + struct.pack('>H', len(self.server_info.data.ticket_buf[host])) + self.server_info.data.ticket_buf[host]
|
||||
ext += binascii.unhexlify(b"000d001600140601060305010503040104030301030302010203")
|
||||
ext += binascii.unhexlify(b"000500050100000000")
|
||||
ext += binascii.unhexlify(b"00120000")
|
||||
@@ -149,6 +156,8 @@ class tls_ticket_auth(plain.plain):
|
||||
verify = buf[11:33]
|
||||
if hmac.new(self.server_info.key + self.server_info.data.client_id, verify, hashlib.sha1).digest()[:10] != buf[33:43]:
|
||||
raise Exception('client_decode data error')
|
||||
if hmac.new(self.server_info.key + self.server_info.data.client_id, buf[:-10], hashlib.sha1).digest()[:10] != buf[-10:]:
|
||||
raise Exception('client_decode data error')
|
||||
return (b'', True)
|
||||
|
||||
def server_encode(self, buf):
|
||||
@@ -167,13 +176,19 @@ class tls_ticket_auth(plain.plain):
|
||||
data = self.tls_version + self.pack_auth_data(self.client_id) + b"\x20" + self.client_id + binascii.unhexlify(b"c02f000005ff01000100")
|
||||
data = b"\x02\x00" + struct.pack('>H', len(data)) + data #server hello
|
||||
data = b"\x16\x03\x03" + struct.pack('>H', len(data)) + data
|
||||
if random.randint(0, 8) < 1:
|
||||
ticket = os.urandom((struct.unpack('>H', os.urandom(2))[0] % 164) * 2 + 64)
|
||||
ticket = struct.pack('>H', len(ticket) + 4) + b"\x04\x00" + struct.pack('>H', len(ticket))
|
||||
data += b"\x16" + self.tls_version + ticket #New session ticket
|
||||
data += b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec
|
||||
data += b"\x16" + self.tls_version + b"\x00\x20" + os.urandom(22) #Finished
|
||||
finish_len = random.choice([32, 40])
|
||||
data += b"\x16" + self.tls_version + struct.pack('>H', finish_len) + os.urandom(finish_len - 10) #Finished
|
||||
data += hmac.new(self.server_info.key + self.client_id, data, hashlib.sha1).digest()[:10]
|
||||
return data
|
||||
|
||||
def decode_error_return(self, buf):
|
||||
self.handshake_status = -1
|
||||
self.overhead = 0
|
||||
if self.method == 'tls1.2_ticket_auth':
|
||||
return (b'E'*2048, False, False)
|
||||
return (buf, True, False)
|
||||
@@ -200,19 +215,19 @@ class tls_ticket_auth(plain.plain):
|
||||
self.recv_buffer += buf
|
||||
buf = self.recv_buffer
|
||||
verify = buf
|
||||
verify_len = 43 - 10
|
||||
if len(buf) < 43:
|
||||
raise Exception('server_decode data error')
|
||||
if not match_begin(buf, b"\x14" + self.tls_version + b"\x00\x01\x01"): #ChangeCipherSpec
|
||||
raise Exception('server_decode data error')
|
||||
buf = buf[6:]
|
||||
if not match_begin(buf, b"\x16" + self.tls_version + b"\x00\x20"): #Finished
|
||||
if not match_begin(buf, b"\x16" + self.tls_version + b"\x00"): #Finished
|
||||
raise Exception('server_decode data error')
|
||||
verify_len = struct.unpack('>H', buf[3:5])[0] + 1 # 11 - 10
|
||||
if len(verify) < verify_len + 10:
|
||||
raise Exception('server_decode data error')
|
||||
if hmac.new(self.server_info.key + self.client_id, verify[:verify_len], hashlib.sha1).digest()[:10] != verify[verify_len:verify_len+10]:
|
||||
raise Exception('server_decode data error')
|
||||
if len(buf) < 37:
|
||||
raise Exception('server_decode data error')
|
||||
self.recv_buffer = buf[37:]
|
||||
self.recv_buffer = verify[verify_len + 10:]
|
||||
self.handshake_status = 8
|
||||
return self.server_decode(b'')
|
||||
|
||||
|
||||
@@ -40,6 +40,12 @@ class plain(object):
|
||||
def init_data(self):
|
||||
return b''
|
||||
|
||||
def get_overhead(self, direction): # direction: true for c->s false for s->c
|
||||
return 0
|
||||
|
||||
def get_server_info(self):
|
||||
return self.server_info
|
||||
|
||||
def set_server_info(self, server_info):
|
||||
self.server_info = server_info
|
||||
|
||||
@@ -79,7 +85,7 @@ class plain(object):
|
||||
return buf
|
||||
|
||||
def server_udp_post_decrypt(self, buf):
|
||||
return buf
|
||||
return (buf, None)
|
||||
|
||||
def dispose(self):
|
||||
pass
|
||||
|
||||
@@ -350,11 +350,11 @@ class verify_sha1(verify_base):
|
||||
def server_udp_post_decrypt(self, buf):
|
||||
if buf and ((ord(buf[0]) & 0x10) == 0x10):
|
||||
if len(buf) <= 11:
|
||||
return b''
|
||||
return (b'', None)
|
||||
sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, buf[:-10], hashlib.sha1).digest()[:10]
|
||||
if sha1data != buf[-10:]:
|
||||
return b''
|
||||
return to_bytes(chr(ord(buf[0]) & 0xEF)) + buf[1:-10]
|
||||
return (b'', None)
|
||||
return (to_bytes(chr(ord(buf[0]) & 0xEF)) + buf[1:-10], None)
|
||||
else:
|
||||
return buf
|
||||
return (buf, None)
|
||||
|
||||
|
||||
@@ -96,8 +96,8 @@ def main():
|
||||
protocol_param = password_obfs.get('protocol_param', protocol_param)
|
||||
obfs = password_obfs.get('obfs', obfs)
|
||||
obfs_param = password_obfs.get('obfs_param', obfs_param)
|
||||
bind = password_obfs.get('bind', bind)
|
||||
bindv6 = password_obfs.get('bindv6', bindv6)
|
||||
bind = password_obfs.get('out_bind', bind)
|
||||
bindv6 = password_obfs.get('out_bindv6', bindv6)
|
||||
else:
|
||||
password = password_obfs
|
||||
a_config = config.copy()
|
||||
|
||||
Regular → Executable
+53
-60
@@ -52,49 +52,37 @@ def print_exception(e):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def __version():
|
||||
version_str = ''
|
||||
try:
|
||||
import pkg_resources
|
||||
version_str = pkg_resources.get_distribution('shadowsocks').version
|
||||
except Exception:
|
||||
try:
|
||||
from shadowsocks import version
|
||||
version_str = version.version()
|
||||
except Exception:
|
||||
pass
|
||||
return version_str
|
||||
|
||||
def print_shadowsocks():
|
||||
version_str = ''
|
||||
try:
|
||||
import pkg_resources
|
||||
version_str = pkg_resources.get_distribution('shadowsocks').version
|
||||
except Exception:
|
||||
try:
|
||||
from shadowsocks import version
|
||||
version_str = version.version()
|
||||
except Exception:
|
||||
pass
|
||||
print('ShadowsocksR %s' % version_str)
|
||||
print('ShadowsocksR %s' % __version())
|
||||
|
||||
def log_shadowsocks_version():
|
||||
version_str = ''
|
||||
try:
|
||||
import pkg_resources
|
||||
version_str = pkg_resources.get_distribution('shadowsocks').version
|
||||
except Exception:
|
||||
try:
|
||||
from shadowsocks import version
|
||||
version_str = version.version()
|
||||
except Exception:
|
||||
pass
|
||||
logging.info('ShadowsocksR %s' % version_str)
|
||||
logging.info('ShadowsocksR %s' % __version())
|
||||
|
||||
|
||||
def find_config():
|
||||
config_path = 'user-config.json'
|
||||
if os.path.exists(config_path):
|
||||
return config_path
|
||||
config_path = os.path.join(os.path.dirname(__file__), '../', 'user-config.json')
|
||||
if os.path.exists(config_path):
|
||||
return config_path
|
||||
|
||||
user_config_path = 'user-config.json'
|
||||
config_path = 'config.json'
|
||||
if os.path.exists(config_path):
|
||||
return config_path
|
||||
config_path = os.path.join(os.path.dirname(__file__), '../', 'config.json')
|
||||
if os.path.exists(config_path):
|
||||
return config_path
|
||||
return None
|
||||
|
||||
def sub_find(file_name):
|
||||
if os.path.exists(file_name):
|
||||
return file_name
|
||||
file_name = os.path.join(os.path.abspath('..'), file_name)
|
||||
return file_name if os.path.exists(file_name) else None
|
||||
|
||||
return sub_find(user_config_path) or sub_find(config_path)
|
||||
|
||||
def check_config(config, is_local):
|
||||
if config.get('daemon', None) == 'stop':
|
||||
@@ -119,21 +107,21 @@ def check_config(config, is_local):
|
||||
config['server_port'] = int(config['server_port'])
|
||||
|
||||
if config.get('local_address', '') in [b'0.0.0.0']:
|
||||
logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe')
|
||||
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.warn('warning: server set to listen on %s:%s, are you sure?' %
|
||||
logging.warning('warning: server set to listen on %s:%s, are you sure?' %
|
||||
(to_str(config['server']), config['server_port']))
|
||||
if (config.get('method', '') or '').lower() == 'table':
|
||||
logging.warn('warning: table is not safe; please use a safer cipher, '
|
||||
logging.warning('warning: table is not safe; please use a safer cipher, '
|
||||
'like AES-256-CFB')
|
||||
if (config.get('method', '') or '').lower() == 'rc4':
|
||||
logging.warn('warning: RC4 is not safe; please use a safer cipher, '
|
||||
logging.warning('warning: RC4 is not safe; please use a safer cipher, '
|
||||
'like AES-256-CFB')
|
||||
if config.get('timeout', 300) < 100:
|
||||
logging.warn('warning: your timeout %d seems too short' %
|
||||
logging.warning('warning: your timeout %d seems too short' %
|
||||
int(config.get('timeout')))
|
||||
if config.get('timeout', 300) > 600:
|
||||
logging.warn('warning: your timeout %d seems too long' %
|
||||
logging.warning('warning: your timeout %d seems too long' %
|
||||
int(config.get('timeout')))
|
||||
if config.get('password') in [b'mypassword']:
|
||||
logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your '
|
||||
@@ -149,7 +137,8 @@ def check_config(config, is_local):
|
||||
|
||||
def get_config(is_local):
|
||||
global verbose
|
||||
|
||||
config = {}
|
||||
config_path = None
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(levelname)-s: %(message)s')
|
||||
if is_local:
|
||||
@@ -161,23 +150,32 @@ def get_config(is_local):
|
||||
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=',
|
||||
'forbidden-ip=', 'user=', 'manager-address=', 'version']
|
||||
try:
|
||||
config_path = find_config()
|
||||
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
|
||||
for key, value in optlist:
|
||||
if key == '-c':
|
||||
config_path = value
|
||||
elif key in ('-h', '--help'):
|
||||
print_help(is_local)
|
||||
sys.exit(0)
|
||||
elif key == '--version':
|
||||
print_shadowsocks()
|
||||
sys.exit(0)
|
||||
else:
|
||||
continue
|
||||
|
||||
if config_path is None:
|
||||
config_path = find_config()
|
||||
|
||||
|
||||
if config_path:
|
||||
logging.info('loading config from %s' % config_path)
|
||||
logging.debug('loading config from %s' % config_path)
|
||||
with open(config_path, 'rb') as f:
|
||||
try:
|
||||
config = parse_json_in_str(remove_comment(f.read().decode('utf8')))
|
||||
except ValueError as e:
|
||||
logging.error('found an error in config.json: %s',
|
||||
e.message)
|
||||
logging.error('found an error in config.json: %s', str(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
|
||||
v_count = 0
|
||||
for key, value in optlist:
|
||||
@@ -217,15 +215,7 @@ def get_config(is_local):
|
||||
config['user'] = to_str(value)
|
||||
elif key == '--forbidden-ip':
|
||||
config['forbidden_ip'] = to_str(value)
|
||||
elif key in ('-h', '--help'):
|
||||
if is_local:
|
||||
print_local_help()
|
||||
else:
|
||||
print_server_help()
|
||||
sys.exit(0)
|
||||
elif key == '--version':
|
||||
print_shadowsocks()
|
||||
sys.exit(0)
|
||||
|
||||
elif key == '-d':
|
||||
config['daemon'] = to_str(value)
|
||||
elif key == '--pid-file':
|
||||
@@ -235,6 +225,8 @@ def get_config(is_local):
|
||||
elif key == '-q':
|
||||
v_count -= 1
|
||||
config['verbose'] = v_count
|
||||
else:
|
||||
continue
|
||||
except getopt.GetoptError as e:
|
||||
print(e, file=sys.stderr)
|
||||
print_help(is_local)
|
||||
@@ -252,13 +244,14 @@ def get_config(is_local):
|
||||
config['obfs'] = to_str(config.get('obfs', 'plain'))
|
||||
config['obfs_param'] = to_str(config.get('obfs_param', ''))
|
||||
config['port_password'] = config.get('port_password', None)
|
||||
config['additional_ports'] = config.get('additional_ports', {})
|
||||
config['timeout'] = int(config.get('timeout', 300))
|
||||
config['udp_timeout'] = int(config.get('udp_timeout', 120))
|
||||
config['udp_cache'] = int(config.get('udp_cache', 64))
|
||||
config['fast_open'] = config.get('fast_open', False)
|
||||
config['workers'] = config.get('workers', 1)
|
||||
config['pid-file'] = config.get('pid-file', '/var/run/shadowsocks.pid')
|
||||
config['log-file'] = config.get('log-file', '/var/log/shadowsocks.log')
|
||||
config['pid-file'] = config.get('pid-file', '/var/run/shadowsocksr.pid')
|
||||
config['log-file'] = config.get('log-file', '/var/log/shadowsocksr.log')
|
||||
config['verbose'] = config.get('verbose', False)
|
||||
config['connect_verbose_info'] = config.get('connect_verbose_info', 0)
|
||||
config['local_address'] = to_str(config.get('local_address', '127.0.0.1'))
|
||||
@@ -285,7 +278,7 @@ def get_config(is_local):
|
||||
sys.exit(2)
|
||||
try:
|
||||
config['ignore_bind'] = \
|
||||
IPNetwork(config.get('ignore_bind', '127.0.0.0/8,::1/128'))
|
||||
IPNetwork(config.get('ignore_bind', '127.0.0.0/8,::1/128,10.0.0.0/8,192.168.0.0/16'))
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
sys.exit(2)
|
||||
|
||||
+237
-41
@@ -27,6 +27,8 @@ import binascii
|
||||
import traceback
|
||||
import random
|
||||
import platform
|
||||
import threading
|
||||
from collections import deque
|
||||
|
||||
from shadowsocks import encrypt, obfs, eventloop, shell, common, lru_cache
|
||||
from shadowsocks.common import pre_parse_header, parse_header
|
||||
@@ -89,9 +91,38 @@ WAIT_STATUS_READING = 1
|
||||
WAIT_STATUS_WRITING = 2
|
||||
WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING
|
||||
|
||||
NETWORK_MTU = 1492
|
||||
TCP_MSS = NETWORK_MTU - 40
|
||||
BUF_SIZE = 32 * 1024
|
||||
UDP_MAX_BUF_SIZE = 65536
|
||||
|
||||
class SpeedTester(object):
|
||||
def __init__(self, max_speed = 0):
|
||||
self.max_speed = max_speed * 1024
|
||||
self.timeout = 1
|
||||
self._cache = deque()
|
||||
self.sum_len = 0
|
||||
|
||||
def update_limit(self, max_speed):
|
||||
self.max_speed = max_speed * 1024
|
||||
|
||||
def add(self, data_len):
|
||||
if self.max_speed > 0:
|
||||
self._cache.append((time.time(), data_len))
|
||||
self.sum_len += data_len
|
||||
|
||||
def isExceed(self):
|
||||
if self.max_speed > 0:
|
||||
if self.sum_len > 0:
|
||||
cut_t = time.time()
|
||||
t = max(cut_t - self._cache[0][0], 0.01)
|
||||
speed = self.sum_len / t
|
||||
if self._cache[0][0] + self.timeout < cut_t:
|
||||
self.sum_len -= self._cache[0][1]
|
||||
self._cache.popleft()
|
||||
return speed >= self.max_speed
|
||||
return False
|
||||
|
||||
class TCPRelayHandler(object):
|
||||
def __init__(self, server, fd_to_handlers, loop, local_sock, config,
|
||||
dns_resolver, is_local):
|
||||
@@ -106,6 +137,9 @@ class TCPRelayHandler(object):
|
||||
self._dns_resolver = dns_resolver
|
||||
self._client_address = local_sock.getpeername()[:2]
|
||||
self._accept_address = local_sock.getsockname()[:2]
|
||||
self._user = None
|
||||
self._user_id = server._listen_port
|
||||
self._tcp_mss = TCP_MSS
|
||||
|
||||
# TCP Relay works as either sslocal or ssserver
|
||||
# if is_local, this is sslocal
|
||||
@@ -120,9 +154,21 @@ class TCPRelayHandler(object):
|
||||
return
|
||||
self._encrypt_correct = True
|
||||
self._obfs = obfs.obfs(config['obfs'])
|
||||
self._protocol = obfs.obfs(config['protocol'])
|
||||
self._overhead = self._obfs.get_overhead(self._is_local) + self._protocol.get_overhead(self._is_local)
|
||||
self._recv_buffer_size = BUF_SIZE - self._overhead
|
||||
|
||||
try:
|
||||
self._tcp_mss = local_sock.getsockopt(socket.SOL_TCP, socket.TCP_MAXSEG)
|
||||
logging.debug("TCP MSS = %d" % (self._tcp_mss,))
|
||||
except:
|
||||
pass
|
||||
|
||||
server_info = obfs.server_info(server.obfs_data)
|
||||
server_info.host = config['server']
|
||||
server_info.port = server._listen_port
|
||||
#server_info.users = server.server_users
|
||||
#server_info.update_user_func = self._update_user
|
||||
server_info.client = self._client_address[0]
|
||||
server_info.client_port = self._client_address[1]
|
||||
server_info.protocol_param = ''
|
||||
@@ -132,13 +178,15 @@ class TCPRelayHandler(object):
|
||||
server_info.key_str = common.to_bytes(config['password'])
|
||||
server_info.key = self._encryptor.cipher_key
|
||||
server_info.head_len = 30
|
||||
server_info.tcp_mss = 1460
|
||||
server_info.tcp_mss = self._tcp_mss
|
||||
server_info.buffer_size = self._recv_buffer_size
|
||||
self._obfs.set_server_info(server_info)
|
||||
|
||||
self._protocol = obfs.obfs(config['protocol'])
|
||||
server_info = obfs.server_info(server.protocol_data)
|
||||
server_info.host = config['server']
|
||||
server_info.port = server._listen_port
|
||||
server_info.users = server.server_users
|
||||
server_info.update_user_func = self._update_user
|
||||
server_info.client = self._client_address[0]
|
||||
server_info.client_port = self._client_address[1]
|
||||
server_info.protocol_param = config['protocol_param']
|
||||
@@ -148,7 +196,8 @@ class TCPRelayHandler(object):
|
||||
server_info.key_str = common.to_bytes(config['password'])
|
||||
server_info.key = self._encryptor.cipher_key
|
||||
server_info.head_len = 30
|
||||
server_info.tcp_mss = 1460
|
||||
server_info.tcp_mss = self._tcp_mss
|
||||
server_info.buffer_size = self._recv_buffer_size
|
||||
self._protocol.set_server_info(server_info)
|
||||
|
||||
self._redir_list = config.get('redirect', ["*#0.0.0.0:0"])
|
||||
@@ -183,6 +232,8 @@ class TCPRelayHandler(object):
|
||||
self._update_activity()
|
||||
self._server.add_connection(1)
|
||||
self._server.stat_add(self._client_address[0], 1)
|
||||
self.speed_tester_u = SpeedTester(config.get("speed_limit_per_con", 0))
|
||||
self.speed_tester_d = SpeedTester(config.get("speed_limit_per_con", 0))
|
||||
|
||||
def __hash__(self):
|
||||
# default __hash__ is id / 16
|
||||
@@ -203,6 +254,10 @@ class TCPRelayHandler(object):
|
||||
logging.debug('chosen server: %s:%d', server, server_port)
|
||||
return server, server_port
|
||||
|
||||
def _update_user(self, user):
|
||||
self._user = user
|
||||
self._user_id = struct.unpack('<I', user)[0]
|
||||
|
||||
def _update_activity(self, data_len=0):
|
||||
# tell the TCP Relay we have activities recently
|
||||
# else it will think we are inactive and timed out
|
||||
@@ -303,8 +358,8 @@ class TCPRelayHandler(object):
|
||||
try:
|
||||
if self._encrypt_correct:
|
||||
if sock == self._remote_sock:
|
||||
self._server.server_transfer_ul += len(data)
|
||||
self._update_activity(len(data))
|
||||
self._server.add_transfer_u(self._user, len(data))
|
||||
self._update_activity(len(data))
|
||||
if data:
|
||||
l = len(data)
|
||||
s = sock.send(data)
|
||||
@@ -396,18 +451,16 @@ class TCPRelayHandler(object):
|
||||
items_sum = common.to_str(host).rsplit('#', 1)
|
||||
items_match = common.to_str(items_sum[0]).rsplit(':', 1)
|
||||
items = common.to_str(items_sum[1]).rsplit(':', 1)
|
||||
if len(items_match) > 1:
|
||||
if self._server._listen_port != int(items_match[1]):
|
||||
continue
|
||||
match_port = 0
|
||||
if len(items_match) > 1:
|
||||
if items_match[1] != "*":
|
||||
try:
|
||||
match_port = int(items_match[1])
|
||||
if self._server._listen_port != int(items_match[1]) and int(items_match[1]) != 0:
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
if items_match[0] != "*" and common.match_regex(items_match[0], ogn_data) == False and \
|
||||
not (match_port == self._server._listen_port or match_port == 0):
|
||||
|
||||
if items_match[0] != "*" and common.match_regex(
|
||||
items_match[0], ogn_data) == False:
|
||||
continue
|
||||
if len(items) > 1:
|
||||
try:
|
||||
@@ -421,7 +474,7 @@ class TCPRelayHandler(object):
|
||||
return ("0.0.0.0", 0)
|
||||
|
||||
def _handel_protocol_error(self, client_address, ogn_data):
|
||||
logging.warn("Protocol ERROR, TCP ogn data %s from %s:%d via port %d" % (binascii.hexlify(ogn_data), client_address[0], client_address[1], self._server._listen_port))
|
||||
logging.warn("Protocol ERROR, TCP ogn data %s from %s:%d via port %d by UID %d" % (binascii.hexlify(ogn_data), client_address[0], client_address[1], self._server._listen_port, self._user_id))
|
||||
self._encrypt_correct = False
|
||||
#create redirect or disconnect by hash code
|
||||
host, port = self._get_redirect_host(client_address, ogn_data)
|
||||
@@ -530,10 +583,16 @@ class TCPRelayHandler(object):
|
||||
if header_result is None:
|
||||
data = self._handel_protocol_error(self._client_address, ogn_data)
|
||||
header_result = parse_header(data)
|
||||
self._overhead = self._obfs.get_overhead(self._is_local) + self._protocol.get_overhead(self._is_local)
|
||||
self._recv_buffer_size = BUF_SIZE - self._overhead
|
||||
server_info = self._obfs.get_server_info()
|
||||
server_info.buffer_size = self._recv_buffer_size
|
||||
server_info = self._protocol.get_server_info()
|
||||
server_info.buffer_size = self._recv_buffer_size
|
||||
connecttype, remote_addr, remote_port, header_length = header_result
|
||||
common.connect_log('%s connecting %s:%d via port %d' %
|
||||
common.connect_log('%s connecting %s:%d via port %d by UID %d' %
|
||||
((connecttype == 0) and 'TCP' or 'UDP',
|
||||
common.to_str(remote_addr), remote_port, self._server._listen_port))
|
||||
common.to_str(remote_addr), remote_port, self._server._listen_port, self._user_id))
|
||||
self._remote_address = (common.to_str(remote_addr), remote_port)
|
||||
self._remote_udp = (connecttype != 0)
|
||||
# pause reading
|
||||
@@ -602,15 +661,15 @@ class TCPRelayHandler(object):
|
||||
if self._forbidden_iplist:
|
||||
if common.to_str(sa[0]) in self._forbidden_iplist:
|
||||
if self._remote_address:
|
||||
raise Exception('IP %s is in forbidden list, when connect to %s:%d via port %d' %
|
||||
(common.to_str(sa[0]), self._remote_address[0], self._remote_address[1], self._server._listen_port))
|
||||
raise Exception('IP %s is in forbidden list, when connect to %s:%d via port %d by UID %d' %
|
||||
(common.to_str(sa[0]), self._remote_address[0], self._remote_address[1], self._server._listen_port, self._user_id))
|
||||
raise Exception('IP %s is in forbidden list, reject' %
|
||||
common.to_str(sa[0]))
|
||||
if self._forbidden_portset:
|
||||
if sa[1] in self._forbidden_portset:
|
||||
if self._remote_address:
|
||||
raise Exception('Port %d is in forbidden list, when connect to %s:%d via port %d' %
|
||||
(sa[1], self._remote_address[0], self._remote_address[1], self._server._listen_port))
|
||||
raise Exception('Port %d is in forbidden list, when connect to %s:%d via port %d by UID %d' %
|
||||
(sa[1], self._remote_address[0], self._remote_address[1], self._server._listen_port, self._user_id))
|
||||
raise Exception('Port %d is in forbidden list, reject' % sa[1])
|
||||
remote_sock = socket.socket(af, socktype, proto)
|
||||
self._remote_sock = remote_sock
|
||||
@@ -700,15 +759,30 @@ class TCPRelayHandler(object):
|
||||
logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
|
||||
self.destroy()
|
||||
|
||||
def _get_read_size(self, sock, recv_buffer_size):
|
||||
if self._overhead == 0:
|
||||
return recv_buffer_size
|
||||
buffer_size = len(sock.recv(recv_buffer_size, socket.MSG_PEEK))
|
||||
if buffer_size == recv_buffer_size:
|
||||
return buffer_size
|
||||
s = buffer_size % self._tcp_mss + self._overhead
|
||||
if s > self._tcp_mss:
|
||||
return buffer_size + s - self._tcp_mss
|
||||
return buffer_size
|
||||
|
||||
def _on_local_read(self):
|
||||
# handle all local read events and dispatch them to methods for
|
||||
# each stage
|
||||
if not self._local_sock:
|
||||
return
|
||||
is_local = self._is_local
|
||||
if is_local:
|
||||
recv_buffer_size = self._get_read_size(self._local_sock, self._recv_buffer_size)
|
||||
else:
|
||||
recv_buffer_size = BUF_SIZE
|
||||
data = None
|
||||
try:
|
||||
data = self._local_sock.recv(BUF_SIZE)
|
||||
data = self._local_sock.recv(recv_buffer_size)
|
||||
except (OSError, IOError) as e:
|
||||
if eventloop.errno_from_exception(e) in \
|
||||
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
|
||||
@@ -716,6 +790,9 @@ class TCPRelayHandler(object):
|
||||
if not data:
|
||||
self.destroy()
|
||||
return
|
||||
|
||||
self.speed_tester_u.add(len(data))
|
||||
self._server.speed_tester_u(self._user_id).add(len(data))
|
||||
ogn_data = data
|
||||
if not is_local:
|
||||
if self._encryptor is not None:
|
||||
@@ -802,7 +879,11 @@ class TCPRelayHandler(object):
|
||||
data = struct.pack('>H', size) + data
|
||||
#logging.info('UDP over TCP recvfrom %s:%d %d bytes to %s:%d' % (addr[0], addr[1], len(data), self._client_address[0], self._client_address[1]))
|
||||
else:
|
||||
data = self._remote_sock.recv(BUF_SIZE)
|
||||
if self._is_local:
|
||||
recv_buffer_size = BUF_SIZE
|
||||
else:
|
||||
recv_buffer_size = self._get_read_size(self._remote_sock, self._recv_buffer_size)
|
||||
data = self._remote_sock.recv(recv_buffer_size)
|
||||
except (OSError, IOError) as e:
|
||||
if eventloop.errno_from_exception(e) in \
|
||||
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK, 10035): #errno.WSAEWOULDBLOCK
|
||||
@@ -810,6 +891,9 @@ class TCPRelayHandler(object):
|
||||
if not data:
|
||||
self.destroy()
|
||||
return
|
||||
|
||||
self.speed_tester_d.add(len(data))
|
||||
self._server.speed_tester_d(self._user_id).add(len(data))
|
||||
if self._encryptor is not None:
|
||||
if self._is_local:
|
||||
try:
|
||||
@@ -838,8 +922,8 @@ class TCPRelayHandler(object):
|
||||
data = self._protocol.server_pre_encrypt(data)
|
||||
data = self._encryptor.encrypt(data)
|
||||
data = self._obfs.server_encode(data)
|
||||
self._update_activity(len(data))
|
||||
self._server.server_transfer_dl += len(data)
|
||||
self._server.add_transfer_d(self._user, len(data))
|
||||
self._update_activity(len(data))
|
||||
else:
|
||||
return
|
||||
try:
|
||||
@@ -871,53 +955,71 @@ class TCPRelayHandler(object):
|
||||
self._update_stream(STREAM_UP, WAIT_STATUS_READING)
|
||||
|
||||
def _on_local_error(self):
|
||||
logging.debug('got local error')
|
||||
if self._local_sock:
|
||||
logging.error(eventloop.get_sock_error(self._local_sock))
|
||||
logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
|
||||
err = eventloop.get_sock_error(self._local_sock)
|
||||
if err.errno not in [errno.ECONNRESET, errno.EPIPE]:
|
||||
logging.error(err)
|
||||
logging.error("local error, exception from %s:%d" % (self._client_address[0], self._client_address[1]))
|
||||
self.destroy()
|
||||
|
||||
def _on_remote_error(self):
|
||||
logging.debug('got remote error')
|
||||
if self._remote_sock:
|
||||
logging.error(eventloop.get_sock_error(self._remote_sock))
|
||||
if self._remote_address:
|
||||
logging.error("when connect to %s:%d" % (self._remote_address[0], self._remote_address[1]))
|
||||
else:
|
||||
logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
|
||||
err = eventloop.get_sock_error(self._remote_sock)
|
||||
if err.errno not in [errno.ECONNRESET]:
|
||||
logging.error(err)
|
||||
if self._remote_address:
|
||||
logging.error("remote error, when connect to %s:%d" % (self._remote_address[0], self._remote_address[1]))
|
||||
else:
|
||||
logging.error("remote error, exception from %s:%d" % (self._client_address[0], self._client_address[1]))
|
||||
self.destroy()
|
||||
|
||||
def handle_event(self, sock, event):
|
||||
# handle all events in this handler and dispatch them to methods
|
||||
handle = False
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
logging.debug('ignore handle_event: destroyed')
|
||||
return
|
||||
return True
|
||||
if self._user is not None and self._user not in self._server.server_users:
|
||||
self.destroy()
|
||||
return True
|
||||
# order is important
|
||||
if sock == self._remote_sock or sock == self._remote_sock_v6:
|
||||
if event & eventloop.POLL_ERR:
|
||||
handle = True
|
||||
self._on_remote_error()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
return True
|
||||
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
|
||||
self._on_remote_read(sock == self._remote_sock)
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
if not self.speed_tester_d.isExceed():
|
||||
if not self._server.speed_tester_d(self._user_id).isExceed():
|
||||
handle = True
|
||||
self._on_remote_read(sock == self._remote_sock)
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return True
|
||||
if event & eventloop.POLL_OUT:
|
||||
handle = True
|
||||
self._on_remote_write()
|
||||
elif sock == self._local_sock:
|
||||
if event & eventloop.POLL_ERR:
|
||||
handle = True
|
||||
self._on_local_error()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
return True
|
||||
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
|
||||
self._on_local_read()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
if not self.speed_tester_u.isExceed():
|
||||
if not self._server.speed_tester_u(self._user_id).isExceed():
|
||||
handle = True
|
||||
self._on_local_read()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return True
|
||||
if event & eventloop.POLL_OUT:
|
||||
handle = True
|
||||
self._on_local_write()
|
||||
else:
|
||||
logging.warn('unknown socket from %s:%d' % (self._client_address[0], self._client_address[1]))
|
||||
|
||||
return handle
|
||||
|
||||
def _log_error(self, e):
|
||||
logging.error('%s when handling connection from %s:%d' %
|
||||
(e, self._client_address[0], self._client_address[1]))
|
||||
@@ -989,6 +1091,12 @@ class TCPRelay(object):
|
||||
self._fd_to_handlers = {}
|
||||
self.server_transfer_ul = 0
|
||||
self.server_transfer_dl = 0
|
||||
self.server_users = {}
|
||||
self.server_user_transfer_ul = {}
|
||||
self.server_user_transfer_dl = {}
|
||||
self.mu = False
|
||||
self._speed_tester_u = {}
|
||||
self._speed_tester_d = {}
|
||||
self.server_connections = 0
|
||||
self.protocol_data = obfs.obfs(config['protocol']).init_data()
|
||||
self.obfs_data = obfs.obfs(config['obfs']).init_data()
|
||||
@@ -1008,6 +1116,9 @@ class TCPRelay(object):
|
||||
listen_port = config['server_port']
|
||||
self._listen_port = listen_port
|
||||
|
||||
if common.to_bytes(config['protocol']) in [b"auth_aes128_md5", b"auth_aes128_sha1"]:
|
||||
self._update_users(None, None)
|
||||
|
||||
addrs = socket.getaddrinfo(listen_addr, listen_port, 0,
|
||||
socket.SOCK_STREAM, socket.SOL_TCP)
|
||||
if len(addrs) == 0:
|
||||
@@ -1047,6 +1158,91 @@ class TCPRelay(object):
|
||||
self.server_connections += val
|
||||
logging.debug('server port %5d connections = %d' % (self._listen_port, self.server_connections,))
|
||||
|
||||
def get_ud(self):
|
||||
return (self.server_transfer_ul, self.server_transfer_dl)
|
||||
|
||||
def get_users_ud(self):
|
||||
return (self.server_user_transfer_ul.copy(), self.server_user_transfer_dl.copy())
|
||||
|
||||
def _update_users(self, protocol_param, acl):
|
||||
if protocol_param is None:
|
||||
protocol_param = self._config['protocol_param']
|
||||
param = common.to_bytes(protocol_param).split(b'#')
|
||||
if len(param) == 2:
|
||||
self.mu = True
|
||||
user_list = param[1].split(b',')
|
||||
if user_list:
|
||||
for user in user_list:
|
||||
items = user.split(b':')
|
||||
if len(items) == 2:
|
||||
user_int_id = int(items[0])
|
||||
uid = struct.pack('<I', user_int_id)
|
||||
if acl is not None and user_int_id not in acl:
|
||||
self.del_user(uid)
|
||||
else:
|
||||
passwd = items[1]
|
||||
self.add_user(uid, passwd)
|
||||
|
||||
def update_user(self, id, passwd):
|
||||
uid = struct.pack('<I', id)
|
||||
self.add_user(uid, passwd)
|
||||
|
||||
def update_users(self, users):
|
||||
for uid in list(self.server_users.keys()):
|
||||
id = struct.unpack('<I', uid)[0]
|
||||
if id not in users:
|
||||
self.del_user(uid)
|
||||
for id in users:
|
||||
uid = struct.pack('<I', id)
|
||||
self.add_user(uid, users[id])
|
||||
|
||||
def add_user(self, user, passwd): # user: binstr[4], passwd: str
|
||||
self.server_users[user] = common.to_bytes(passwd)
|
||||
|
||||
def del_user(self, user):
|
||||
if user in self.server_users:
|
||||
del self.server_users[user]
|
||||
|
||||
def add_transfer_u(self, user, transfer):
|
||||
if user is None:
|
||||
self.server_transfer_ul += transfer
|
||||
else:
|
||||
if user not in self.server_user_transfer_ul:
|
||||
self.server_user_transfer_ul[user] = 0
|
||||
self.server_user_transfer_ul[user] += transfer + self.server_transfer_ul
|
||||
self.server_transfer_ul = 0
|
||||
|
||||
def add_transfer_d(self, user, transfer):
|
||||
if user is None:
|
||||
self.server_transfer_dl += transfer
|
||||
else:
|
||||
if user not in self.server_user_transfer_dl:
|
||||
self.server_user_transfer_dl[user] = 0
|
||||
self.server_user_transfer_dl[user] += transfer + self.server_transfer_dl
|
||||
self.server_transfer_dl = 0
|
||||
|
||||
def speed_tester_u(self, uid):
|
||||
if uid not in self._speed_tester_u:
|
||||
if self.mu: #TODO
|
||||
self._speed_tester_u[uid] = SpeedTester(self._config.get("speed_limit_per_user", 0))
|
||||
else:
|
||||
self._speed_tester_u[uid] = SpeedTester(self._config.get("speed_limit_per_user", 0))
|
||||
return self._speed_tester_u[uid]
|
||||
|
||||
def speed_tester_d(self, uid):
|
||||
if uid not in self._speed_tester_d:
|
||||
if self.mu: #TODO
|
||||
self._speed_tester_d[uid] = SpeedTester(self._config.get("speed_limit_per_user", 0))
|
||||
else:
|
||||
self._speed_tester_d[uid] = SpeedTester(self._config.get("speed_limit_per_user", 0))
|
||||
return self._speed_tester_d[uid]
|
||||
|
||||
def update_limit(self, uid, max_speed):
|
||||
if uid in self._speed_tester_u:
|
||||
self._speed_tester_u[uid].update_limit(max_speed)
|
||||
if uid in self._speed_tester_d:
|
||||
self._speed_tester_d[uid].update_limit(max_speed)
|
||||
|
||||
def update_stat(self, port, stat_dict, val):
|
||||
newval = stat_dict.get(0, 0) + val
|
||||
stat_dict[0] = newval
|
||||
|
||||
+138
-33
@@ -70,13 +70,11 @@ import errno
|
||||
import random
|
||||
import binascii
|
||||
import traceback
|
||||
import threading
|
||||
|
||||
from shadowsocks import encrypt, obfs, eventloop, lru_cache, common, shell
|
||||
from shadowsocks.common import pre_parse_header, parse_header, pack_addr
|
||||
|
||||
# we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time
|
||||
TIMEOUTS_CLEAN_SIZE = 512
|
||||
|
||||
# for each handler, we have 2 stream directions:
|
||||
# upstream: from client to server direction
|
||||
# read local and write to remote
|
||||
@@ -781,35 +779,44 @@ class TCPRelayHandler(object):
|
||||
|
||||
def handle_event(self, sock, event):
|
||||
# handle all events in this handler and dispatch them to methods
|
||||
handle = False
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
logging.debug('ignore handle_event: destroyed')
|
||||
return
|
||||
return True
|
||||
# order is important
|
||||
if sock == self._remote_sock:
|
||||
if event & eventloop.POLL_ERR:
|
||||
handle = True
|
||||
self._on_remote_error()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
return True
|
||||
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
|
||||
handle = True
|
||||
self._on_remote_read()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
return True
|
||||
if event & eventloop.POLL_OUT:
|
||||
handle = True
|
||||
self._on_remote_write()
|
||||
elif sock == self._local_sock:
|
||||
if event & eventloop.POLL_ERR:
|
||||
handle = True
|
||||
self._on_local_error()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
return True
|
||||
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
|
||||
handle = True
|
||||
self._on_local_read()
|
||||
if self._stage == STAGE_DESTROYED:
|
||||
return
|
||||
return True
|
||||
if event & eventloop.POLL_OUT:
|
||||
handle = True
|
||||
self._on_local_write()
|
||||
else:
|
||||
logging.warn('unknown socket')
|
||||
|
||||
return handle
|
||||
|
||||
def _log_error(self, e):
|
||||
logging.error('%s when handling connection from %s' %
|
||||
(e, self._client_address.keys()))
|
||||
@@ -888,21 +895,28 @@ class UDPRelay(object):
|
||||
self._is_local = is_local
|
||||
self._udp_cache_size = config['udp_cache']
|
||||
self._cache = lru_cache.LRUCache(timeout=config['udp_timeout'],
|
||||
close_callback=self._close_client)
|
||||
close_callback=self._close_client_pair)
|
||||
self._cache_dns_client = lru_cache.LRUCache(timeout=10,
|
||||
close_callback=self._close_client)
|
||||
close_callback=self._close_client_pair)
|
||||
self._client_fd_to_server_addr = {}
|
||||
self._dns_cache = lru_cache.LRUCache(timeout=300)
|
||||
self._dns_cache = lru_cache.LRUCache(timeout=1800)
|
||||
self._eventloop = None
|
||||
self._closed = False
|
||||
self.server_transfer_ul = 0
|
||||
self.server_transfer_dl = 0
|
||||
self.server_users = {}
|
||||
self.server_user_transfer_ul = {}
|
||||
self.server_user_transfer_dl = {}
|
||||
|
||||
if common.to_bytes(config['protocol']) in [b"auth_aes128_md5", b"auth_aes128_sha1"]:
|
||||
self._update_users(None, None)
|
||||
|
||||
self.protocol_data = obfs.obfs(config['protocol']).init_data()
|
||||
self._protocol = obfs.obfs(config['protocol'])
|
||||
server_info = obfs.server_info(self.protocol_data)
|
||||
server_info.host = self._listen_addr
|
||||
server_info.port = self._listen_port
|
||||
server_info.users = self.server_users
|
||||
server_info.protocol_param = config['protocol_param']
|
||||
server_info.obfs_param = ''
|
||||
server_info.iv = b''
|
||||
@@ -910,7 +924,8 @@ class UDPRelay(object):
|
||||
server_info.key_str = common.to_bytes(config['password'])
|
||||
server_info.key = encrypt.encrypt_key(self._password, self._method)
|
||||
server_info.head_len = 30
|
||||
server_info.tcp_mss = 1440
|
||||
server_info.tcp_mss = 1452
|
||||
server_info.buffer_size = BUF_SIZE
|
||||
self._protocol.set_server_info(server_info)
|
||||
|
||||
self._sockets = set()
|
||||
@@ -956,6 +971,73 @@ class UDPRelay(object):
|
||||
logging.debug('chosen server: %s:%d', server, server_port)
|
||||
return server, server_port
|
||||
|
||||
def get_ud(self):
|
||||
return (self.server_transfer_ul, self.server_transfer_dl)
|
||||
|
||||
def get_users_ud(self):
|
||||
ret = (self.server_user_transfer_ul.copy(), self.server_user_transfer_dl.copy())
|
||||
return ret
|
||||
|
||||
def _update_users(self, protocol_param, acl):
|
||||
if protocol_param is None:
|
||||
protocol_param = self._config['protocol_param']
|
||||
param = common.to_bytes(protocol_param).split(b'#')
|
||||
if len(param) == 2:
|
||||
user_list = param[1].split(b',')
|
||||
if user_list:
|
||||
for user in user_list:
|
||||
items = user.split(b':')
|
||||
if len(items) == 2:
|
||||
user_int_id = int(items[0])
|
||||
uid = struct.pack('<I', user_int_id)
|
||||
if acl is not None and user_int_id not in acl:
|
||||
self.del_user(uid)
|
||||
else:
|
||||
passwd = items[1]
|
||||
self.add_user(uid, passwd)
|
||||
|
||||
def update_user(self, id, passwd):
|
||||
uid = struct.pack('<I', id)
|
||||
self.add_user(uid, passwd)
|
||||
|
||||
def update_users(self, users):
|
||||
for uid in list(self.server_users.keys()):
|
||||
id = struct.unpack('<I', uid)[0]
|
||||
if id not in users:
|
||||
self.del_user(uid)
|
||||
for id in users:
|
||||
uid = struct.pack('<I', id)
|
||||
self.add_user(uid, users[id])
|
||||
|
||||
def add_user(self, user, passwd): # user: binstr[4], passwd: str
|
||||
self.server_users[user] = common.to_bytes(passwd)
|
||||
|
||||
def del_user(self, user):
|
||||
if user in self.server_users:
|
||||
del self.server_users[user]
|
||||
|
||||
def add_transfer_u(self, user, transfer):
|
||||
if user is None:
|
||||
self.server_transfer_ul += transfer
|
||||
else:
|
||||
if user not in self.server_user_transfer_ul:
|
||||
self.server_user_transfer_ul[user] = 0
|
||||
self.server_user_transfer_ul[user] += transfer + self.server_transfer_ul
|
||||
self.server_transfer_ul = 0
|
||||
|
||||
def add_transfer_d(self, user, transfer):
|
||||
if user is None:
|
||||
self.server_transfer_dl += transfer
|
||||
else:
|
||||
if user not in self.server_user_transfer_dl:
|
||||
self.server_user_transfer_dl[user] = 0
|
||||
self.server_user_transfer_dl[user] += transfer + self.server_transfer_dl
|
||||
self.server_transfer_dl = 0
|
||||
|
||||
def _close_client_pair(self, client_pair):
|
||||
client, uid = client_pair
|
||||
self._close_client(client)
|
||||
|
||||
def _close_client(self, client):
|
||||
if hasattr(client, 'close'):
|
||||
if not self._is_local:
|
||||
@@ -1039,6 +1121,7 @@ class UDPRelay(object):
|
||||
logging.debug('UDP handle_server: data is empty')
|
||||
if self._stat_callback:
|
||||
self._stat_callback(self._listen_port, len(data))
|
||||
uid = None
|
||||
if self._is_local:
|
||||
frag = common.ord(data[2])
|
||||
if frag != 0:
|
||||
@@ -1054,7 +1137,7 @@ class UDPRelay(object):
|
||||
logging.debug('UDP handle_server: data is empty after decrypt')
|
||||
return
|
||||
self._protocol.obfs.server_info.recv_iv = ref_iv[0]
|
||||
data = self._protocol.server_udp_post_decrypt(data)
|
||||
data, uid = self._protocol.server_udp_post_decrypt(data)
|
||||
|
||||
#logging.info("UDP data %s" % (binascii.hexlify(data),))
|
||||
if not self._is_local:
|
||||
@@ -1080,27 +1163,35 @@ class UDPRelay(object):
|
||||
connecttype, dest_addr, dest_port, header_length = header_result
|
||||
|
||||
if self._is_local:
|
||||
connecttype = 3
|
||||
server_addr, server_port = self._get_a_server()
|
||||
else:
|
||||
server_addr, server_port = dest_addr, dest_port
|
||||
|
||||
addrs = self._dns_cache.get(server_addr, None)
|
||||
if addrs is None:
|
||||
# TODO async getaddrinfo
|
||||
if (connecttype & 7) == 3:
|
||||
addrs = self._dns_cache.get(server_addr, None)
|
||||
if addrs is None:
|
||||
# TODO async getaddrinfo
|
||||
addrs = socket.getaddrinfo(server_addr, server_port, 0,
|
||||
socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||
if not addrs:
|
||||
# drop
|
||||
return
|
||||
else:
|
||||
self._dns_cache[server_addr] = addrs
|
||||
else:
|
||||
addrs = socket.getaddrinfo(server_addr, server_port, 0,
|
||||
socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||
if not addrs:
|
||||
# drop
|
||||
return
|
||||
else:
|
||||
self._dns_cache[server_addr] = addrs
|
||||
|
||||
af, socktype, proto, canonname, sa = addrs[0]
|
||||
key = client_key(r_addr, af)
|
||||
client = self._cache.get(key, None)
|
||||
if not client:
|
||||
client = self._cache_dns_client.get(key, None)
|
||||
if not client:
|
||||
client_pair = self._cache.get(key, None)
|
||||
if not client_pair:
|
||||
client_pair = self._cache_dns_client.get(key, None)
|
||||
if not client_pair:
|
||||
if self._forbidden_iplist:
|
||||
if common.to_str(sa[0]) in self._forbidden_iplist:
|
||||
logging.debug('IP %s is in forbidden list, drop' %
|
||||
@@ -1114,6 +1205,7 @@ class UDPRelay(object):
|
||||
# drop
|
||||
return
|
||||
client = socket.socket(af, socktype, proto)
|
||||
client_uid = uid
|
||||
client.setblocking(False)
|
||||
self._socket_bind_addr(client, af)
|
||||
is_dns = False
|
||||
@@ -1124,9 +1216,9 @@ class UDPRelay(object):
|
||||
#logging.info("unknown data %s" % (binascii.hexlify(data),))
|
||||
if sa[1] == 53 and is_dns: #DNS
|
||||
logging.debug("DNS query %s from %s:%d" % (common.to_str(sa[0]), r_addr[0], r_addr[1]))
|
||||
self._cache_dns_client[key] = client
|
||||
self._cache_dns_client[key] = (client, uid)
|
||||
else:
|
||||
self._cache[key] = client
|
||||
self._cache[key] = (client, uid)
|
||||
self._client_fd_to_server_addr[client.fileno()] = (r_addr, af)
|
||||
|
||||
self._sockets.add(client.fileno())
|
||||
@@ -1134,10 +1226,15 @@ class UDPRelay(object):
|
||||
|
||||
logging.debug('UDP port %5d sockets %d' % (self._listen_port, len(self._sockets)))
|
||||
|
||||
common.connect_log('UDP data to %s:%d via port %d' %
|
||||
if uid is None:
|
||||
user_id = self._listen_port
|
||||
else:
|
||||
user_id = struct.unpack('<I', client_uid)[0]
|
||||
common.connect_log('UDP data to %s:%d via port %d by UID %d' %
|
||||
(common.to_str(server_addr), server_port,
|
||||
self._listen_port))
|
||||
|
||||
self._listen_port, user_id))
|
||||
else:
|
||||
client, client_uid = client_pair
|
||||
self._cache.clear(self._udp_cache_size)
|
||||
self._cache_dns_client.clear(16)
|
||||
|
||||
@@ -1156,7 +1253,7 @@ class UDPRelay(object):
|
||||
try:
|
||||
#logging.info('UDP handle_server sendto %s:%d %d bytes' % (common.to_str(server_addr), server_port, len(data)))
|
||||
client.sendto(data, (server_addr, server_port))
|
||||
self.server_transfer_ul += len(data)
|
||||
self.add_transfer_u(client_uid, len(data))
|
||||
except IOError as e:
|
||||
err = eventloop.errno_from_exception(e)
|
||||
if err in (errno.EINPROGRESS, errno.EAGAIN):
|
||||
@@ -1266,14 +1363,22 @@ class UDPRelay(object):
|
||||
response = b'\x00\x00\x00' + data
|
||||
client_addr = self._client_fd_to_server_addr.get(sock.fileno())
|
||||
if client_addr:
|
||||
self.server_transfer_dl += len(response)
|
||||
self.write_to_server_socket(response, client_addr[0])
|
||||
key = client_key(client_addr[0], client_addr[1])
|
||||
client = self._cache_dns_client.get(key, None)
|
||||
if client:
|
||||
client_pair = self._cache.get(key, None)
|
||||
client_dns_pair = self._cache_dns_client.get(key, None)
|
||||
if client_pair:
|
||||
client, client_uid = client_pair
|
||||
self.add_transfer_d(client_uid, len(response))
|
||||
elif client_dns_pair:
|
||||
client, client_uid = client_dns_pair
|
||||
self.add_transfer_d(client_uid, len(response))
|
||||
else:
|
||||
self.server_transfer_dl += len(response)
|
||||
self.write_to_server_socket(response, client_addr[0])
|
||||
if client_dns_pair:
|
||||
logging.debug("remove dns client %s:%d" % (client_addr[0][0], client_addr[0][1]))
|
||||
del self._cache_dns_client[key]
|
||||
self._close_client(client)
|
||||
self._close_client(client_dns_pair[0])
|
||||
else:
|
||||
# this packet is from somewhere else we know
|
||||
# simply drop that packet
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 breakwa11
|
||||
# Copyright 2017 breakwa11
|
||||
#
|
||||
# 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
|
||||
@@ -16,5 +16,5 @@
|
||||
# under the License.
|
||||
|
||||
def version():
|
||||
return '3.0.1 2017-01-03'
|
||||
return '3.1.2 2017-04-07'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user