70 Commits
Author SHA1 Message Date
破娃酱 51c8ae4d78 bump 2017-04-07 11:55:06 +08:00
破娃酱 862fa890ee typo 2017-04-05 22:11:55 +08:00
破娃酱 d82c931f8e auto adjust TCP_MAXSEG 2017-04-05 21:46:19 +08:00
破娃酱 8e27035851 fix buffer_size 2017-03-26 23:42:32 +08:00
破娃酱 4d9fca9b37 display UID 2017-03-26 19:07:30 +08:00
破娃酱 90ffd1c6ee display UID 2017-03-26 18:29:13 +08:00
破娃酱 922cf462d6 improve rand 2017-03-26 00:00:01 +08:00
破娃酱 76fd4296a5 bump
improve rand size
2017-03-25 02:17:59 +08:00
破娃酱 63cceb29f9 fix Dockerfile 2017-03-25 01:54:23 +08:00
破娃酱 1b1c1fbac6 improve rand size 2017-03-25 01:53:11 +08:00
破娃酱 6cbcd855ff better rand size 2017-03-24 17:07:52 +08:00
破娃酱 bf0055be40 fix Dockerfile 2017-03-23 17:19:15 +08:00
破娃酱 63a6de5d26 fix host type in http_simple 2017-03-23 13:16:58 +08:00
破娃酱 1c58b10dd0 ignore bind 10.0.0.0/8 and 192.168.0.0/16 2017-03-19 18:28:43 +08:00
破娃酱 6712dd62c0 fix accept any size of Finish message 2017-03-18 21:49:50 +08:00
破娃酱 caaacc7090 accept any size of Finish message 2017-03-18 21:35:39 +08:00
破娃酱 6f35105ea2 fix syntax 2017-03-18 18:00:36 +08:00
破娃酱 e19f9e70e7 add "New session ticket" 2017-03-18 17:58:33 +08:00
破娃酱 e3d6efbc7a bump 2017-03-16 00:39:49 +08:00
破娃酱 1355c273db remove bom 2017-03-16 00:35:19 +08:00
破娃酱 bae7d7a0c2 fix str type under py3 2017-03-02 23:20:06 +08:00
破娃酱 e1b199fc44 add additional_ports in config
using user's password as protocol param in multi-user mode
2017-02-19 10:27:25 +08:00
破娃酱 e2957e672e timeout set to 1 2017-02-14 23:28:49 +08:00
破娃酱 f296751def bandwidth limit of the user 2017-02-14 21:36:38 +08:00
破娃酱 c4c1cd6dc1 refine auth_aes128_* & http_* 2017-02-14 20:23:45 +08:00
破娃酱 74083af472 process DNS 2017-02-14 02:55:33 +08:00
破娃酱 6732244ddf lower latency 2017-02-13 02:14:21 +08:00
破娃酱 2723295101 fix CPU usage 2017-02-13 01:38:48 +08:00
破娃酱 85955d6e6d speed limit in KiB 2017-02-12 22:46:09 +08:00
破娃酱 72ca31dad9 add traffic control for each TCP connection 2017-02-12 22:35:26 +08:00
John 2750af4a2e optimize shell (#173) 2017-02-11 22:56:02 +08:00
John bf8bb8afe2 fix undefined variable bug (#171) 2017-02-05 20:44:43 +08:00
破娃酱 5d479087a8 fix tls1.2_ticket_auth when it as a client 2017-02-05 19:18:50 +08:00
破娃酱 26b4d8bc18 Merge branch manyuser into dev 2017-02-05 15:38:47 +08:00
Dwwwwww 3014b0034c Update README.md (#169)
Improve
2017-02-05 12:36:45 +08:00
Esdeath bf443d72ec refine add "glzjinmod" interface
glzjinmod support these key
2017-02-04 13:01:18 +08:00
破娃酱 31aaea603a add "glzjinmod" interface 2017-02-02 19:50:55 +08:00
破娃酱 390be3bf8d random ticket size for client only 2017-02-02 19:05:18 +08:00
破娃酱 e7a61a934f ignore SIGPIPE 2017-01-25 01:08:00 +08:00
破娃酱 96760ed438 not display RST error 2017-01-23 16:30:35 +08:00
破娃酱 00a31285ed encode protocol param to URL with -i 2017-01-22 13:54:56 +08:00
破娃酱 68693d5ec7 fix bug under py3 2017-01-21 12:46:31 +08:00
Esdeath 7cfbe49806 refine traffic log when redirect enable (#163) 2017-01-18 21:05:15 +08:00
破娃酱 9c794624e0 fix auth_aes128_* client side 2017-01-18 17:00:09 +08:00
破娃酱 ccacef1556 fix bug & update usage 2017-01-17 20:53:54 +08:00
破娃酱 75643635d4 change default setting 2017-01-15 10:05:29 +08:00
破娃酱 4048abaeff refine user transfer 2017-01-13 21:52:17 +08:00
破娃酱 d4b7483ef0 modify mgr fast command 2017-01-12 22:06:20 +08:00
破娃酱 ad892e1223 let 'transfer_enable' be an integer 2017-01-11 18:34:28 +08:00
破娃酱 2de2a553d7 multi-user for mudbjson mode 2017-01-11 14:45:37 +08:00
破娃酱 588d8ad8b3 fix auth_aes128_* client side 2017-01-11 14:43:24 +08:00
破娃酱 278ce30c66 update mu online client manager 2017-01-09 17:48:19 +08:00
破娃酱 25c66dab14 bump 2017-01-08 17:06:08 +08:00
破娃酱 de67c11207 typo 2017-01-08 00:47:44 +08:00
破娃酱 4f52dffcc1 parse protocol_param in py3 2017-01-08 00:26:49 +08:00
破娃酱 cd825c9c2c typo 2017-01-07 22:07:13 +08:00
noisyfox e6410c067b Don't clear file first when saving config file 2017-01-07 21:10:49 +11:00
noisyfox f0b0a31baf Revert commit fd9214b334 2017-01-07 20:32:22 +11:00
noisyfox 6d53546f6e Use super() call 2017-01-07 20:03:20 +11:00
noisyfox 8777152ec9 Run db thread as daemon
so that all sub-thread will inherite this and be killed automatically if main thread crash
2017-01-07 19:58:32 +11:00
noisyfox f05bea4e10 Fix crash on windows 2017-01-07 19:34:39 +11:00
noisyfox 0bf1e46ace Update .gitignore 2017-01-07 19:23:30 +11:00
noisyfox 0f8dd36297 Add initcfg.bat for windows 2017-01-07 19:23:09 +11:00
noisyfox 498f71a21b Save json config as utf8 2017-01-07 19:22:36 +11:00
noisyfox 490ae12128 Fix mujson_mgr.py crash when MUDB_FILE doesn't exist 2017-01-07 19:14:58 +11:00
noisyfox 84fd119f26 Fix mujson_mgr.py crash on windows 2017-01-07 19:14:02 +11:00
noisyfox fd9214b334 Use space instead of tab for indent
see PEP8
2017-01-07 18:50:12 +11:00
破娃酱 b367ad0560 using '#' to split multi-user setting 2017-01-07 01:23:42 +08:00
破娃酱 0ca45be89d update MU auth_aes128_* protocol 2017-01-06 21:02:52 +08:00
破娃酱 959aad3f41 multiuser in single port protocol 2017-01-04 22:46:39 +08:00
24 changed files with 923 additions and 304 deletions
+5
View File
@@ -29,3 +29,8 @@ htmlcov
.DS_Store
.idea
# Ignore user config
user-config.json
userapiconfig.py
usermysql.json
+18
View File
@@ -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
View File
@@ -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
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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()
+2 -1
View File
@@ -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
View File
@@ -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
+5 -2
View File
@@ -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()
+2 -2
View File
@@ -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:
+6
View File
@@ -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
View File
@@ -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)
+8 -3
View File
@@ -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)
+22 -7
View File
@@ -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'')
+7 -1
View File
@@ -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
+4 -4
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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'