add manyuser branch
support udp over tcp support chacha20 & salsa20 (base on libsodium)
This commit is contained in:
parent
e001f1818c
commit
f2efed9608
18 changed files with 1327 additions and 57 deletions
12
Config.py
Normal file
12
Config.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
#Config
|
||||
MYSQL_HOST = 'mdss.mengsky.net'
|
||||
MYSQL_PORT = 3306
|
||||
MYSQL_USER = 'ss'
|
||||
MYSQL_PASS = 'ss'
|
||||
MYSQL_DB = 'shadowsocks'
|
||||
|
||||
MANAGE_PASS = 'ss233333333'
|
||||
#if you want manage in other server you should set this value to global ip
|
||||
MANAGE_BIND_IP = '127.0.0.1'
|
||||
#make sure this port is idle
|
||||
MANAGE_PORT = 23333
|
1
__init__.py
Normal file
1
__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#!/usr/bin/python
|
100
asyncmgr.py
Normal file
100
asyncmgr.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import time
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import re
|
||||
import logging
|
||||
from shadowsocks import common
|
||||
from shadowsocks import lru_cache
|
||||
from shadowsocks import eventloop
|
||||
import server_pool
|
||||
import Config
|
||||
|
||||
class ServerMgr(object):
|
||||
|
||||
def __init__(self):
|
||||
self._loop = None
|
||||
self._request_id = 1
|
||||
self._hosts = {}
|
||||
self._hostname_status = {}
|
||||
self._hostname_to_cb = {}
|
||||
self._cb_to_hostname = {}
|
||||
self._last_time = time.time()
|
||||
self._sock = None
|
||||
self._servers = None
|
||||
|
||||
def add_to_loop(self, loop):
|
||||
if self._loop:
|
||||
raise Exception('already add to loop')
|
||||
self._loop = loop
|
||||
# TODO when dns server is IPv6
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.SOL_UDP)
|
||||
self._sock.bind((Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
|
||||
self._sock.setblocking(False)
|
||||
loop.add(self._sock, eventloop.POLL_IN)
|
||||
loop.add_handler(self.handle_events)
|
||||
|
||||
def _handle_data(self, sock):
|
||||
data, addr = sock.recvfrom(128)
|
||||
#manage pwd:port:passwd:action
|
||||
args = data.split(':')
|
||||
if len(args) < 4:
|
||||
return
|
||||
if args[0] == Config.MANAGE_PASS:
|
||||
if args[3] == '0':
|
||||
server_pool.ServerPool.get_instance().cb_del_server(args[1])
|
||||
elif args[3] == '1':
|
||||
server_pool.ServerPool.get_instance().new_server(args[1], args[2])
|
||||
|
||||
def handle_events(self, events):
|
||||
for sock, fd, event in events:
|
||||
if sock != self._sock:
|
||||
continue
|
||||
if event & eventloop.POLL_ERR:
|
||||
logging.error('mgr socket err')
|
||||
self._loop.remove(self._sock)
|
||||
self._sock.close()
|
||||
# TODO when dns server is IPv6
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.SOL_UDP)
|
||||
self._sock.setblocking(False)
|
||||
self._loop.add(self._sock, eventloop.POLL_IN)
|
||||
else:
|
||||
self._handle_data(sock)
|
||||
break
|
||||
|
||||
def close(self):
|
||||
if self._sock:
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
|
||||
|
||||
def test():
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
12
config.json
Normal file
12
config.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"server":"0.0.0.0",
|
||||
"server_ipv6": "::",
|
||||
"server_port":8388,
|
||||
"local_address": "127.0.0.1",
|
||||
"local_port":1080,
|
||||
"password":"m",
|
||||
"timeout":300,
|
||||
"method":"aes-256-cfb",
|
||||
"fast_open": false,
|
||||
"workers": 1
|
||||
}
|
148
db_transfer.py
Normal file
148
db_transfer.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import logging
|
||||
import cymysql
|
||||
import time
|
||||
import sys
|
||||
from server_pool import ServerPool
|
||||
import Config
|
||||
|
||||
class DbTransfer(object):
|
||||
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.last_get_transfer = {}
|
||||
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
if DbTransfer.instance is None:
|
||||
DbTransfer.instance = DbTransfer()
|
||||
return DbTransfer.instance
|
||||
|
||||
def push_db_all_user(self):
|
||||
#更新用户流量到数据库
|
||||
last_transfer = self.last_get_transfer
|
||||
curr_transfer = ServerPool.get_instance().get_servers_transfer()
|
||||
#上次和本次的增量
|
||||
dt_transfer = {}
|
||||
for id in curr_transfer.keys():
|
||||
if id in last_transfer:
|
||||
if last_transfer[id][0] == curr_transfer[id][0] and last_transfer[id][1] == curr_transfer[id][1]:
|
||||
continue
|
||||
elif curr_transfer[id][0] == 0 and curr_transfer[id][1] == 0:
|
||||
continue
|
||||
elif last_transfer[id][0] <= curr_transfer[id][0] and \
|
||||
last_transfer[id][1] <= curr_transfer[id][1]:
|
||||
dt_transfer[id] = [curr_transfer[id][0] - last_transfer[id][0],
|
||||
curr_transfer[id][1] - last_transfer[id][1]]
|
||||
else:
|
||||
dt_transfer[id] = [curr_transfer[id][0], curr_transfer[id][1]]
|
||||
else:
|
||||
if curr_transfer[id][0] == 0 and curr_transfer[id][1] == 0:
|
||||
continue
|
||||
dt_transfer[id] = [curr_transfer[id][0], curr_transfer[id][1]]
|
||||
|
||||
self.last_get_transfer = curr_transfer
|
||||
query_head = 'UPDATE user'
|
||||
query_sub_when = ''
|
||||
query_sub_when2 = ''
|
||||
query_sub_in = None
|
||||
last_time = time.time()
|
||||
for id in dt_transfer.keys():
|
||||
query_sub_when += ' WHEN %s THEN u+%s' % (id, dt_transfer[id][0])
|
||||
query_sub_when2 += ' WHEN %s THEN d+%s' % (id, dt_transfer[id][1])
|
||||
if query_sub_in is not None:
|
||||
query_sub_in += ',%s' % id
|
||||
else:
|
||||
query_sub_in = '%s' % id
|
||||
if query_sub_when == '':
|
||||
return
|
||||
query_sql = query_head + ' SET u = CASE port' + query_sub_when + \
|
||||
' END, d = CASE port' + query_sub_when2 + \
|
||||
' END, t = ' + str(int(last_time)) + \
|
||||
' WHERE port IN (%s)' % query_sub_in
|
||||
#print query_sql
|
||||
conn = cymysql.connect(host=Config.MYSQL_HOST, port=Config.MYSQL_PORT, user=Config.MYSQL_USER,
|
||||
passwd=Config.MYSQL_PASS, db=Config.MYSQL_DB, charset='utf8')
|
||||
cur = conn.cursor()
|
||||
cur.execute(query_sql)
|
||||
cur.close()
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@staticmethod
|
||||
def pull_db_all_user():
|
||||
#数据库所有用户信息
|
||||
conn = cymysql.connect(host=Config.MYSQL_HOST, port=Config.MYSQL_PORT, user=Config.MYSQL_USER,
|
||||
passwd=Config.MYSQL_PASS, db=Config.MYSQL_DB, charset='utf8')
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT port, u, d, transfer_enable, passwd, switch, enable, plan FROM user")
|
||||
rows = []
|
||||
for r in cur.fetchall():
|
||||
rows.append(list(r))
|
||||
cur.close()
|
||||
conn.close()
|
||||
return rows
|
||||
|
||||
@staticmethod
|
||||
def del_server_out_of_bound_safe(last_rows, rows):
|
||||
#停止超流量的服务
|
||||
#启动没超流量的服务
|
||||
#需要动态载入switchrule,以便实时修改规则
|
||||
cur_servers = {}
|
||||
for row in rows:
|
||||
try:
|
||||
import switchrule
|
||||
allow = switchrule.isTurnOn(row[7], row[5]) and row[6] == 1 and row[1] + row[2] < row[3]
|
||||
except Exception, e:
|
||||
allow = False
|
||||
|
||||
cur_servers[row[0]] = row[4]
|
||||
|
||||
if ServerPool.get_instance().server_is_run(row[0]) > 0:
|
||||
if not allow:
|
||||
logging.info('db stop server at port [%s]' % (row[0]))
|
||||
ServerPool.get_instance().del_server(row[0])
|
||||
elif (row[0] in ServerPool.get_instance().tcp_servers_pool and ServerPool.get_instance().tcp_servers_pool[row[0]]._config['password'] != row[4]) \
|
||||
or (row[0] in ServerPool.get_instance().tcp_ipv6_servers_pool and ServerPool.get_instance().tcp_ipv6_servers_pool[row[0]]._config['password'] != row[4]):
|
||||
#password changed
|
||||
logging.info('db stop server at port [%s] reason: password changed' % (row[0]))
|
||||
ServerPool.get_instance().del_server(row[0])
|
||||
elif ServerPool.get_instance().server_run_status(row[0]) is False:
|
||||
if allow:
|
||||
logging.info('db start server at port [%s] pass [%s]' % (row[0], row[4]))
|
||||
ServerPool.get_instance().new_server(row[0], row[4])
|
||||
|
||||
for row in last_rows:
|
||||
if row[0] in cur_servers:
|
||||
if row[4] == cur_servers[row[0]]:
|
||||
pass
|
||||
else:
|
||||
logging.info('db stop server at port [%s] reason: port not exist' % (row[0]))
|
||||
ServerPool.get_instance().del_server(row[0])
|
||||
|
||||
@staticmethod
|
||||
def thread_db():
|
||||
import socket
|
||||
import time
|
||||
timeout = 60
|
||||
socket.setdefaulttimeout(timeout)
|
||||
last_rows = []
|
||||
while True:
|
||||
#logging.warn('db loop')
|
||||
|
||||
try:
|
||||
DbTransfer.get_instance().push_db_all_user()
|
||||
rows = DbTransfer.get_instance().pull_db_all_user()
|
||||
DbTransfer.del_server_out_of_bound_safe(last_rows, rows)
|
||||
last_rows = rows
|
||||
except Exception as e:
|
||||
logging.warn('db thread except:%s' % e)
|
||||
finally:
|
||||
time.sleep(15)
|
||||
|
||||
|
||||
#SQLData.pull_db_all_user()
|
||||
#print DbTransfer.get_instance().test()
|
22
server.py
Normal file
22
server.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import sys
|
||||
import thread
|
||||
import os
|
||||
os.chdir(os.path.split(os.path.realpath(__file__))[0])
|
||||
|
||||
import server_pool
|
||||
import db_transfer
|
||||
|
||||
#def test():
|
||||
# thread.start_new_thread(DbTransfer.thread_db, ())
|
||||
# Api.web_server()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#server_pool.ServerPool.get_instance()
|
||||
#server_pool.ServerPool.get_instance().new_server(2333, '2333')
|
||||
thread.start_new_thread(db_transfer.DbTransfer.thread_db, ())
|
||||
while True:
|
||||
time.sleep(99999)
|
211
server_pool.py
Normal file
211
server_pool.py
Normal file
|
@ -0,0 +1,211 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
from shadowsocks import utils
|
||||
from shadowsocks import eventloop
|
||||
from shadowsocks import tcprelay
|
||||
from shadowsocks import udprelay
|
||||
from shadowsocks import asyncdns
|
||||
import thread
|
||||
import threading
|
||||
import sys
|
||||
import asyncmgr
|
||||
import Config
|
||||
from socket import *
|
||||
|
||||
class ServerPool(object):
|
||||
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
utils.check_python()
|
||||
self.config = utils.get_config(False)
|
||||
utils.print_shadowsocks()
|
||||
self.dns_resolver = asyncdns.DNSResolver()
|
||||
self.mgr = asyncmgr.ServerMgr()
|
||||
self.udp_on = True ### UDP switch =====================================
|
||||
|
||||
self.tcp_servers_pool = {}
|
||||
self.tcp_ipv6_servers_pool = {}
|
||||
self.udp_servers_pool = {}
|
||||
self.udp_ipv6_servers_pool = {}
|
||||
|
||||
self.loop = eventloop.EventLoop()
|
||||
thread.start_new_thread(ServerPool._loop, (self.loop, self.dns_resolver, self.mgr))
|
||||
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
if ServerPool.instance is None:
|
||||
ServerPool.instance = ServerPool()
|
||||
return ServerPool.instance
|
||||
|
||||
@staticmethod
|
||||
def _loop(loop, dns_resolver, mgr):
|
||||
try:
|
||||
mgr.add_to_loop(loop)
|
||||
dns_resolver.add_to_loop(loop)
|
||||
loop.run()
|
||||
except (KeyboardInterrupt, IOError, OSError) as e:
|
||||
logging.error(e)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
os.exit(0)
|
||||
|
||||
def server_is_run(self, port):
|
||||
port = int(port)
|
||||
ret = 0
|
||||
if port in self.tcp_servers_pool:
|
||||
ret = 1
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
ret |= 2
|
||||
return ret
|
||||
|
||||
def server_run_status(self, port):
|
||||
if 'server' in self.config:
|
||||
if port not in self.tcp_servers_pool:
|
||||
return False
|
||||
if 'server_ipv6' in self.config:
|
||||
if port not in self.tcp_ipv6_servers_pool:
|
||||
return False
|
||||
return True
|
||||
|
||||
def new_server(self, port, password):
|
||||
ret = True
|
||||
port = int(port)
|
||||
|
||||
if 'server_ipv6' in self.config:
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
logging.info("server already at %s:%d" % (self.config['server_ipv6'], port))
|
||||
return 'this port server is already running'
|
||||
else:
|
||||
a_config = self.config.copy()
|
||||
a_config['server'] = a_config['server_ipv6']
|
||||
a_config['server_port'] = port
|
||||
a_config['password'] = password
|
||||
try:
|
||||
logging.info("starting server at %s:%d" % (a_config['server'], port))
|
||||
tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False)
|
||||
tcp_server.add_to_loop(self.loop)
|
||||
self.tcp_ipv6_servers_pool.update({port: tcp_server})
|
||||
if self.udp_on:
|
||||
udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False)
|
||||
udp_server.add_to_loop(self.loop)
|
||||
self.udp_ipv6_servers_pool.update({port: udp_server})
|
||||
except Exception, e:
|
||||
logging.warn("IPV6 exception")
|
||||
logging.warn(e)
|
||||
|
||||
if 'server' in self.config:
|
||||
if port in self.tcp_servers_pool:
|
||||
logging.info("server already at %s:%d" % (self.config['server'], port))
|
||||
return 'this port server is already running'
|
||||
else:
|
||||
a_config = self.config.copy()
|
||||
a_config['server_port'] = port
|
||||
a_config['password'] = password
|
||||
try:
|
||||
logging.info("starting server at %s:%d" % (a_config['server'], port))
|
||||
tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False)
|
||||
tcp_server.add_to_loop(self.loop)
|
||||
self.tcp_servers_pool.update({port: tcp_server})
|
||||
if self.udp_on:
|
||||
udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False)
|
||||
udp_server.add_to_loop(self.loop)
|
||||
self.udp_servers_pool.update({port: udp_server})
|
||||
except Exception, e:
|
||||
logging.warn("IPV4 exception")
|
||||
logging.warn(e)
|
||||
|
||||
return True
|
||||
|
||||
def del_server(self, port):
|
||||
port = int(port)
|
||||
logging.info("del server at %d" % port)
|
||||
try:
|
||||
udpsock = socket(AF_INET, SOCK_DGRAM)
|
||||
udpsock.sendto('%s:%s:0:0' % (Config.MANAGE_PASS, port), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
|
||||
udpsock.close()
|
||||
except Exception, e:
|
||||
logging.warn(e)
|
||||
return True
|
||||
|
||||
def cb_del_server(self, port):
|
||||
port = int(port)
|
||||
|
||||
if port not in self.tcp_servers_pool:
|
||||
logging.info("stopped server at %s:%d already stop" % (self.config['server'], port))
|
||||
else:
|
||||
logging.info("stopped server at %s:%d" % (self.config['server'], port))
|
||||
try:
|
||||
self.tcp_servers_pool[port].destroy()
|
||||
del self.tcp_servers_pool[port]
|
||||
except Exception, e:
|
||||
logging.warn(e)
|
||||
if self.udp_on:
|
||||
try:
|
||||
self.udp_servers_pool[port].destroy()
|
||||
del self.udp_servers_pool[port]
|
||||
except Exception, e:
|
||||
logging.warn(e)
|
||||
|
||||
if 'server_ipv6' in self.config:
|
||||
if port not in self.tcp_ipv6_servers_pool:
|
||||
logging.info("stopped server at %s:%d already stop" % (self.config['server_ipv6'], port))
|
||||
else:
|
||||
logging.info("stopped server at %s:%d" % (self.config['server_ipv6'], port))
|
||||
try:
|
||||
self.tcp_ipv6_servers_pool[port].destroy()
|
||||
del self.tcp_ipv6_servers_pool[port]
|
||||
except Exception, e:
|
||||
logging.warn(e)
|
||||
if self.udp_on:
|
||||
try:
|
||||
self.udp_ipv6_servers_pool[port].destroy()
|
||||
del self.udp_ipv6_servers_pool[port]
|
||||
except Exception, e:
|
||||
logging.warn(e)
|
||||
|
||||
return True
|
||||
|
||||
def get_server_transfer(self, port):
|
||||
port = int(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
|
||||
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
|
||||
return ret
|
||||
|
||||
def get_servers_transfer(self):
|
||||
servers = self.tcp_servers_pool.copy()
|
||||
servers.update(self.tcp_ipv6_servers_pool)
|
||||
ret = {}
|
||||
for port in servers.keys():
|
||||
ret[port] = self.get_server_transfer(port)
|
||||
return ret
|
||||
|
39
setup.py
39
setup.py
|
@ -1,39 +0,0 @@
|
|||
import codecs
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
with codecs.open('README.rst', encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="shadowsocks",
|
||||
version="2.6.11",
|
||||
license='http://www.apache.org/licenses/LICENSE-2.0',
|
||||
description="A fast tunnel proxy that help you get through firewalls",
|
||||
author='clowwindy',
|
||||
author_email='clowwindy42@gmail.com',
|
||||
url='https://github.com/shadowsocks/shadowsocks',
|
||||
packages=['shadowsocks', 'shadowsocks.crypto'],
|
||||
package_data={
|
||||
'shadowsocks': ['README.rst', 'LICENSE']
|
||||
},
|
||||
install_requires=[],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
sslocal = shadowsocks.local:main
|
||||
ssserver = shadowsocks.server:main
|
||||
""",
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Internet :: Proxy Servers',
|
||||
],
|
||||
long_description=long_description,
|
||||
)
|
24
shadowsocks.sql
Normal file
24
shadowsocks.sql
Normal file
|
@ -0,0 +1,24 @@
|
|||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
CREATE TABLE `user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(32) NOT NULL,
|
||||
`pass` varchar(16) NOT NULL,
|
||||
`passwd` varchar(16) NOT NULL,
|
||||
`t` int(11) NOT NULL DEFAULT '0',
|
||||
`u` bigint(20) NOT NULL,
|
||||
`d` bigint(20) NOT NULL,
|
||||
`transfer_enable` bigint(20) NOT NULL,
|
||||
`port` int(11) NOT NULL,
|
||||
`switch` tinyint(4) NOT NULL DEFAULT '1',
|
||||
`enable` tinyint(4) NOT NULL DEFAULT '1',
|
||||
`type` tinyint(4) NOT NULL DEFAULT '1',
|
||||
`last_get_gift_time` int(11) NOT NULL DEFAULT '0',
|
||||
`last_rest_pass_time` int(11) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`,`port`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=415 DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of user
|
||||
-- ----------------------------
|
||||
INSERT INTO `user` VALUES ('7', 'test@test.com', '123456', '0000000', '1410609560', '0', '0', '9320666234', '50000', '1', '1', '7', '0', '0');
|
|
@ -144,6 +144,8 @@ def parse_header(data):
|
|||
dest_addr = None
|
||||
dest_port = None
|
||||
header_length = 0
|
||||
connecttype = (addrtype & 8) and 1 or 0
|
||||
addrtype &= ~8
|
||||
if addrtype == ADDRTYPE_IPV4:
|
||||
if len(data) >= 7:
|
||||
dest_addr = socket.inet_ntoa(data[1:5])
|
||||
|
@ -175,7 +177,7 @@ def parse_header(data):
|
|||
'encryption method' % addrtype)
|
||||
if dest_addr is None:
|
||||
return None
|
||||
return addrtype, to_bytes(dest_addr), dest_port, header_length
|
||||
return connecttype, to_bytes(dest_addr), dest_port, header_length
|
||||
|
||||
|
||||
class IPNetwork(object):
|
||||
|
|
135
shadowsocks/crypto/ctypes_libsodium.py
Normal file
135
shadowsocks/crypto/ctypes_libsodium.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import logging
|
||||
from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \
|
||||
create_string_buffer, c_void_p
|
||||
|
||||
__all__ = ['ciphers']
|
||||
|
||||
libsodium = None
|
||||
loaded = False
|
||||
|
||||
buf_size = 2048
|
||||
|
||||
# for salsa20 and chacha20
|
||||
BLOCK_SIZE = 64
|
||||
|
||||
|
||||
def load_libsodium():
|
||||
global loaded, libsodium, buf
|
||||
|
||||
from ctypes.util import find_library
|
||||
for p in ('sodium',):
|
||||
libsodium_path = find_library(p)
|
||||
if libsodium_path:
|
||||
break
|
||||
else:
|
||||
raise Exception('libsodium not found')
|
||||
logging.info('loading libsodium from %s', libsodium_path)
|
||||
libsodium = CDLL(libsodium_path)
|
||||
libsodium.sodium_init.restype = c_int
|
||||
libsodium.crypto_stream_salsa20_xor_ic.restype = c_int
|
||||
libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p,
|
||||
c_ulonglong,
|
||||
c_char_p, c_ulonglong,
|
||||
c_char_p)
|
||||
libsodium.crypto_stream_chacha20_xor_ic.restype = c_int
|
||||
libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p,
|
||||
c_ulonglong,
|
||||
c_char_p, c_ulonglong,
|
||||
c_char_p)
|
||||
|
||||
libsodium.sodium_init()
|
||||
|
||||
buf = create_string_buffer(buf_size)
|
||||
loaded = True
|
||||
|
||||
|
||||
class Salsa20Crypto(object):
|
||||
def __init__(self, cipher_name, key, iv, op):
|
||||
if not loaded:
|
||||
load_libsodium()
|
||||
self.key = key
|
||||
self.iv = iv
|
||||
self.key_ptr = c_char_p(key)
|
||||
self.iv_ptr = c_char_p(iv)
|
||||
if cipher_name == b'salsa20':
|
||||
self.cipher = libsodium.crypto_stream_salsa20_xor_ic
|
||||
elif cipher_name == b'chacha20':
|
||||
self.cipher = libsodium.crypto_stream_chacha20_xor_ic
|
||||
else:
|
||||
raise Exception('Unknown cipher')
|
||||
# byte counter, not block counter
|
||||
self.counter = 0
|
||||
|
||||
def update(self, data):
|
||||
global buf_size, buf
|
||||
l = len(data)
|
||||
|
||||
# we can only prepend some padding to make the encryption align to
|
||||
# blocks
|
||||
padding = self.counter % BLOCK_SIZE
|
||||
if buf_size < padding + l:
|
||||
buf_size = (padding + l) * 2
|
||||
buf = create_string_buffer(buf_size)
|
||||
|
||||
if padding:
|
||||
data = (b'\0' * padding) + data
|
||||
self.cipher(byref(buf), c_char_p(data), padding + l,
|
||||
self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr)
|
||||
self.counter += l
|
||||
# buf is copied to a str object when we access buf.raw
|
||||
# strip off the padding
|
||||
return buf.raw[padding:padding + l]
|
||||
|
||||
|
||||
ciphers = {
|
||||
b'salsa20': (32, 8, Salsa20Crypto),
|
||||
b'chacha20': (32, 8, Salsa20Crypto),
|
||||
}
|
||||
|
||||
|
||||
def test_salsa20():
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1)
|
||||
decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
def test_chacha20():
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1)
|
||||
decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_chacha20()
|
||||
test_salsa20()
|
188
shadowsocks/crypto/ctypes_openssl.py
Normal file
188
shadowsocks/crypto/ctypes_openssl.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import logging
|
||||
from ctypes import CDLL, c_char_p, c_int, c_long, byref,\
|
||||
create_string_buffer, c_void_p
|
||||
|
||||
__all__ = ['ciphers']
|
||||
|
||||
libcrypto = None
|
||||
loaded = False
|
||||
|
||||
buf_size = 2048
|
||||
|
||||
|
||||
def load_openssl():
|
||||
global loaded, libcrypto, buf
|
||||
|
||||
from ctypes.util import find_library
|
||||
for p in ('crypto', 'eay32', 'libeay32'):
|
||||
libcrypto_path = find_library(p)
|
||||
if libcrypto_path:
|
||||
break
|
||||
else:
|
||||
raise Exception('libcrypto(OpenSSL) not found')
|
||||
logging.info('loading libcrypto from %s', libcrypto_path)
|
||||
libcrypto = CDLL(libcrypto_path)
|
||||
libcrypto.EVP_get_cipherbyname.restype = c_void_p
|
||||
libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p
|
||||
|
||||
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
|
||||
c_char_p, c_char_p, c_int)
|
||||
|
||||
libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
|
||||
c_char_p, c_int)
|
||||
|
||||
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
|
||||
libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
|
||||
if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
|
||||
libcrypto.OpenSSL_add_all_ciphers()
|
||||
|
||||
buf = create_string_buffer(buf_size)
|
||||
loaded = True
|
||||
|
||||
|
||||
def load_cipher(cipher_name):
|
||||
func_name = b'EVP_' + cipher_name.replace(b'-', b'_')
|
||||
if bytes != str:
|
||||
func_name = str(func_name, 'utf-8')
|
||||
cipher = getattr(libcrypto, func_name, None)
|
||||
if cipher:
|
||||
cipher.restype = c_void_p
|
||||
return cipher()
|
||||
return None
|
||||
|
||||
|
||||
class CtypesCrypto(object):
|
||||
def __init__(self, cipher_name, key, iv, op):
|
||||
if not loaded:
|
||||
load_openssl()
|
||||
self._ctx = None
|
||||
cipher = libcrypto.EVP_get_cipherbyname(cipher_name)
|
||||
if not cipher:
|
||||
cipher = load_cipher(cipher_name)
|
||||
if not cipher:
|
||||
raise Exception('cipher %s not found in libcrypto' % cipher_name)
|
||||
key_ptr = c_char_p(key)
|
||||
iv_ptr = c_char_p(iv)
|
||||
self._ctx = libcrypto.EVP_CIPHER_CTX_new()
|
||||
if not self._ctx:
|
||||
raise Exception('can not create cipher context')
|
||||
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
|
||||
key_ptr, iv_ptr, c_int(op))
|
||||
if not r:
|
||||
self.clean()
|
||||
raise Exception('can not initialize cipher context')
|
||||
|
||||
def update(self, data):
|
||||
global buf_size, buf
|
||||
cipher_out_len = c_long(0)
|
||||
l = len(data)
|
||||
if buf_size < l:
|
||||
buf_size = l * 2
|
||||
buf = create_string_buffer(buf_size)
|
||||
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
|
||||
byref(cipher_out_len), c_char_p(data), l)
|
||||
# buf is copied to a str object when we access buf.raw
|
||||
return buf.raw[:cipher_out_len.value]
|
||||
|
||||
def __del__(self):
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
if self._ctx:
|
||||
libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
|
||||
libcrypto.EVP_CIPHER_CTX_free(self._ctx)
|
||||
|
||||
|
||||
ciphers = {
|
||||
b'aes-128-cfb': (16, 16, CtypesCrypto),
|
||||
b'aes-192-cfb': (24, 16, CtypesCrypto),
|
||||
b'aes-256-cfb': (32, 16, CtypesCrypto),
|
||||
b'aes-128-ofb': (16, 16, CtypesCrypto),
|
||||
b'aes-192-ofb': (24, 16, CtypesCrypto),
|
||||
b'aes-256-ofb': (32, 16, CtypesCrypto),
|
||||
b'aes-128-ctr': (16, 16, CtypesCrypto),
|
||||
b'aes-192-ctr': (24, 16, CtypesCrypto),
|
||||
b'aes-256-ctr': (32, 16, CtypesCrypto),
|
||||
b'aes-128-cfb8': (16, 16, CtypesCrypto),
|
||||
b'aes-192-cfb8': (24, 16, CtypesCrypto),
|
||||
b'aes-256-cfb8': (32, 16, CtypesCrypto),
|
||||
b'aes-128-cfb1': (16, 16, CtypesCrypto),
|
||||
b'aes-192-cfb1': (24, 16, CtypesCrypto),
|
||||
b'aes-256-cfb1': (32, 16, CtypesCrypto),
|
||||
b'bf-cfb': (16, 8, CtypesCrypto),
|
||||
b'camellia-128-cfb': (16, 16, CtypesCrypto),
|
||||
b'camellia-192-cfb': (24, 16, CtypesCrypto),
|
||||
b'camellia-256-cfb': (32, 16, CtypesCrypto),
|
||||
b'cast5-cfb': (16, 8, CtypesCrypto),
|
||||
b'des-cfb': (8, 8, CtypesCrypto),
|
||||
b'idea-cfb': (16, 8, CtypesCrypto),
|
||||
b'rc2-cfb': (16, 8, CtypesCrypto),
|
||||
b'rc4': (16, 0, CtypesCrypto),
|
||||
b'seed-cfb': (16, 16, CtypesCrypto),
|
||||
}
|
||||
|
||||
|
||||
def run_method(method):
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1)
|
||||
decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
def test_aes_128_cfb():
|
||||
run_method(b'aes-128-cfb')
|
||||
|
||||
|
||||
def test_aes_256_cfb():
|
||||
run_method(b'aes-256-cfb')
|
||||
|
||||
|
||||
def test_aes_128_cfb8():
|
||||
run_method(b'aes-128-cfb8')
|
||||
|
||||
|
||||
def test_aes_256_ofb():
|
||||
run_method(b'aes-256-ofb')
|
||||
|
||||
|
||||
def test_aes_256_ctr():
|
||||
run_method(b'aes-256-ctr')
|
||||
|
||||
|
||||
def test_bf_cfb():
|
||||
run_method(b'bf-cfb')
|
||||
|
||||
|
||||
def test_rc4():
|
||||
run_method(b'rc4')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_aes_128_cfb()
|
117
shadowsocks/crypto/m2.py
Normal file
117
shadowsocks/crypto/m2.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
__all__ = ['ciphers']
|
||||
|
||||
has_m2 = True
|
||||
try:
|
||||
__import__('M2Crypto')
|
||||
except ImportError:
|
||||
has_m2 = False
|
||||
|
||||
|
||||
def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1,
|
||||
padding=1):
|
||||
|
||||
import M2Crypto.EVP
|
||||
return M2Crypto.EVP.Cipher(alg.replace('-', '_'), key, iv, op,
|
||||
key_as_bytes=0, d='md5', salt=None, i=1,
|
||||
padding=1)
|
||||
|
||||
|
||||
def err(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, padding=1):
|
||||
logging.error(('M2Crypto is required to use %s, please run'
|
||||
' `apt-get install python-m2crypto`') % alg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if has_m2:
|
||||
ciphers = {
|
||||
b'aes-128-cfb': (16, 16, create_cipher),
|
||||
b'aes-192-cfb': (24, 16, create_cipher),
|
||||
b'aes-256-cfb': (32, 16, create_cipher),
|
||||
b'bf-cfb': (16, 8, create_cipher),
|
||||
b'camellia-128-cfb': (16, 16, create_cipher),
|
||||
b'camellia-192-cfb': (24, 16, create_cipher),
|
||||
b'camellia-256-cfb': (32, 16, create_cipher),
|
||||
b'cast5-cfb': (16, 8, create_cipher),
|
||||
b'des-cfb': (8, 8, create_cipher),
|
||||
b'idea-cfb': (16, 8, create_cipher),
|
||||
b'rc2-cfb': (16, 8, create_cipher),
|
||||
b'rc4': (16, 0, create_cipher),
|
||||
b'seed-cfb': (16, 16, create_cipher),
|
||||
}
|
||||
else:
|
||||
ciphers = {}
|
||||
|
||||
|
||||
def run_method(method):
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = create_cipher(method, b'k' * 32, b'i' * 16, 1)
|
||||
decipher = create_cipher(method, b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
def check_env():
|
||||
# skip this test on pypy and Python 3
|
||||
try:
|
||||
import __pypy__
|
||||
del __pypy__
|
||||
from nose.plugins.skip import SkipTest
|
||||
raise SkipTest
|
||||
except ImportError:
|
||||
pass
|
||||
if bytes != str:
|
||||
from nose.plugins.skip import SkipTest
|
||||
raise SkipTest
|
||||
|
||||
|
||||
def test_aes_128_cfb():
|
||||
check_env()
|
||||
run_method(b'aes-128-cfb')
|
||||
|
||||
|
||||
def test_aes_256_cfb():
|
||||
check_env()
|
||||
run_method(b'aes-256-cfb')
|
||||
|
||||
|
||||
def test_bf_cfb():
|
||||
check_env()
|
||||
run_method(b'bf-cfb')
|
||||
|
||||
|
||||
def test_rc4():
|
||||
check_env()
|
||||
run_method(b'rc4')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_aes_128_cfb()
|
|
@ -47,6 +47,8 @@ def try_cipher(key, method=None):
|
|||
def EVP_BytesToKey(password, key_len, iv_len):
|
||||
# equivalent to OpenSSL's EVP_BytesToKey() with count 1
|
||||
# so that we make the same key and iv as nodejs version
|
||||
if hasattr(password, 'encode'):
|
||||
password = password.encode('utf-8')
|
||||
cached_key = '%s-%d-%d' % (password, key_len, iv_len)
|
||||
r = cached_keys.get(cached_key, None)
|
||||
if r:
|
||||
|
|
|
@ -101,6 +101,7 @@ class TCPRelayHandler(object):
|
|||
self._loop = loop
|
||||
self._local_sock = local_sock
|
||||
self._remote_sock = None
|
||||
self._remote_udp = False
|
||||
self._config = config
|
||||
self._dns_resolver = dns_resolver
|
||||
|
||||
|
@ -190,6 +191,28 @@ class TCPRelayHandler(object):
|
|||
# and update the stream to wait for writing
|
||||
if not data or not sock:
|
||||
return False
|
||||
#logging.debug("_write_to_sock %s %s %s" % (self._remote_sock, sock, self._remote_udp))
|
||||
if self._remote_sock == sock and self._remote_udp:
|
||||
try:
|
||||
addrtype = data[3]
|
||||
if addrtype == '\x01':
|
||||
addr = socket.inet_ntoa(data[4:8])
|
||||
port = struct.unpack('>H', data[8:10])[0]
|
||||
logging.info('udp sendto %s:%d %d bytes from %s:%d' % (addr, port, len(data), self._client_address[0], self._client_address[1]))
|
||||
sock.sendto(data[10:], (addr, port))
|
||||
elif addrtype == '\x04':
|
||||
addr = socket.inet_ntop(data[4:20])
|
||||
port = struct.unpack('>H', data[20:22])[0]
|
||||
logging.info('udp sendto %s:%d %d bytes from %s:%d' % (addr, port, len(data), self._client_address[0], self._client_address[1]))
|
||||
sock.sendto(data[22:], (addr, port))
|
||||
elif addrtype == '\x03':
|
||||
#unsupport
|
||||
pass
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
logging.error(trace)
|
||||
return True
|
||||
|
||||
uncomplete = False
|
||||
try:
|
||||
l = len(data)
|
||||
|
@ -203,6 +226,7 @@ class TCPRelayHandler(object):
|
|||
errno.EWOULDBLOCK):
|
||||
uncomplete = True
|
||||
else:
|
||||
#traceback.print_exc()
|
||||
shell.print_exception(e)
|
||||
self.destroy()
|
||||
return False
|
||||
|
@ -291,11 +315,13 @@ class TCPRelayHandler(object):
|
|||
header_result = parse_header(data)
|
||||
if header_result is None:
|
||||
raise Exception('can not parse header')
|
||||
addrtype, remote_addr, remote_port, header_length = header_result
|
||||
logging.info('connecting %s:%d from %s:%d' %
|
||||
(common.to_str(remote_addr), remote_port,
|
||||
connecttype, remote_addr, remote_port, header_length = header_result
|
||||
logging.info('%s connecting %s:%d from %s:%d' %
|
||||
((connecttype == 0) and 'tcp' or 'udp',
|
||||
common.to_str(remote_addr), remote_port,
|
||||
self._client_address[0], self._client_address[1]))
|
||||
self._remote_address = (common.to_str(remote_addr), remote_port)
|
||||
self._remote_udp = (connecttype != 0)
|
||||
# pause reading
|
||||
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
|
||||
self._stage = STAGE_DNS
|
||||
|
@ -323,8 +349,10 @@ class TCPRelayHandler(object):
|
|||
self.destroy()
|
||||
|
||||
def _create_remote_socket(self, ip, port):
|
||||
addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM,
|
||||
socket.SOL_TCP)
|
||||
if self._remote_udp:
|
||||
addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||
else:
|
||||
addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP)
|
||||
if len(addrs) == 0:
|
||||
raise Exception("getaddrinfo failed for %s:%d" % (ip, port))
|
||||
af, socktype, proto, canonname, sa = addrs[0]
|
||||
|
@ -336,6 +364,9 @@ class TCPRelayHandler(object):
|
|||
self._remote_sock = remote_sock
|
||||
self._fd_to_handlers[remote_sock.fileno()] = self
|
||||
remote_sock.setblocking(False)
|
||||
if self._remote_udp:
|
||||
pass
|
||||
else:
|
||||
remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||
return remote_sock
|
||||
|
||||
|
@ -368,6 +399,10 @@ class TCPRelayHandler(object):
|
|||
# else do connect
|
||||
remote_sock = self._create_remote_socket(remote_addr,
|
||||
remote_port)
|
||||
if self._remote_udp:
|
||||
self._loop.add(remote_sock,
|
||||
eventloop.POLL_IN)
|
||||
else:
|
||||
try:
|
||||
remote_sock.connect((remote_addr, remote_port))
|
||||
except (OSError, IOError) as e:
|
||||
|
@ -407,6 +442,7 @@ class TCPRelayHandler(object):
|
|||
data = self._encryptor.decrypt(data)
|
||||
if not data:
|
||||
return
|
||||
self._server.server_transfer_ul += len(data)
|
||||
if self._stage == STAGE_STREAM:
|
||||
if self._is_local:
|
||||
data = self._encryptor.encrypt(data)
|
||||
|
@ -428,14 +464,26 @@ class TCPRelayHandler(object):
|
|||
self._update_activity()
|
||||
data = None
|
||||
try:
|
||||
if self._remote_udp:
|
||||
data, addr = self._remote_sock.recvfrom(BUF_SIZE)
|
||||
port = struct.pack('>H', addr[1])
|
||||
try:
|
||||
ip = socket.inet_aton(addr[0])
|
||||
data = '\x00\x00\x00\x01' + ip + port + data
|
||||
except Exception as e:
|
||||
ip = socket.inet_pton(socket.AF_INET6, addr[0])
|
||||
data = '\x00\x00\x00\x04' + ip + port + data
|
||||
logging.info('udp 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)
|
||||
except (OSError, IOError) as e:
|
||||
if eventloop.errno_from_exception(e) in \
|
||||
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
|
||||
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK, 10035): #errno.WSAEWOULDBLOCK
|
||||
return
|
||||
if not data:
|
||||
self.destroy()
|
||||
return
|
||||
self._server.server_transfer_dl += len(data)
|
||||
if self._is_local:
|
||||
data = self._encryptor.decrypt(data)
|
||||
else:
|
||||
|
@ -472,12 +520,16 @@ class TCPRelayHandler(object):
|
|||
logging.debug('got local error')
|
||||
if self._local_sock:
|
||||
logging.error(eventloop.get_sock_error(self._local_sock))
|
||||
trace = traceback.format_exc()
|
||||
logging.error(trace)
|
||||
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))
|
||||
trace = traceback.format_exc()
|
||||
logging.error(trace)
|
||||
self.destroy()
|
||||
|
||||
def handle_event(self, sock, event):
|
||||
|
@ -558,6 +610,8 @@ class TCPRelay(object):
|
|||
self._eventloop = None
|
||||
self._fd_to_handlers = {}
|
||||
self._last_time = time.time()
|
||||
self.server_transfer_ul = 0L
|
||||
self.server_transfer_dl = 0L
|
||||
|
||||
self._timeout = config['timeout']
|
||||
self._timeouts = [] # a list for all the handlers
|
||||
|
|
|
@ -173,6 +173,7 @@ class UDPRelay(object):
|
|||
client = self._cache.get(key, None)
|
||||
if not client:
|
||||
# TODO async getaddrinfo
|
||||
logging.info('UDP handle_server %s:%d from %s:%d' % (common.to_str(server_addr), server_port, self._listen_addr, self._listen_port))
|
||||
addrs = socket.getaddrinfo(server_addr, server_port, 0,
|
||||
socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||
if addrs:
|
||||
|
|
278
shadowsocks/utils.py
Normal file
278
shadowsocks/utils.py
Normal file
|
@ -0,0 +1,278 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import getopt
|
||||
import logging
|
||||
|
||||
|
||||
VERBOSE_LEVEL = 5
|
||||
|
||||
|
||||
def check_python():
|
||||
info = sys.version_info
|
||||
if not (info[0] == 2 and info[1] >= 6):
|
||||
print 'Python 2.6 or 2.7 required'
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_shadowsocks():
|
||||
version = ''
|
||||
try:
|
||||
import pkg_resources
|
||||
version = pkg_resources.get_distribution('shadowsocks').version
|
||||
except Exception:
|
||||
pass
|
||||
print 'shadowsocks %s' % version
|
||||
|
||||
|
||||
def find_config():
|
||||
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 check_config(config):
|
||||
if config.get('local_address', '') in ['0.0.0.0']:
|
||||
logging.warn('warning: local set to listen 0.0.0.0, which is not safe')
|
||||
if config.get('server', '') in ['127.0.0.1', 'localhost']:
|
||||
logging.warn('warning: server set to listen %s:%s, are you sure?' %
|
||||
(config['server'], config['server_port']))
|
||||
if (config.get('method', '') or '').lower() == '':
|
||||
logging.warn('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, '
|
||||
'like AES-256-CFB')
|
||||
if config.get('timeout', 300) < 100:
|
||||
logging.warn('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' %
|
||||
int(config.get('timeout')))
|
||||
if config.get('password') in ['mypassword', 'barfoo!']:
|
||||
logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your '
|
||||
'config.json!')
|
||||
exit(1)
|
||||
|
||||
|
||||
def get_config(is_local):
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(levelname)-s: %(message)s', filemode='a+')
|
||||
if is_local:
|
||||
shortopts = 'hs:b:p:k:l:m:c:t:vq'
|
||||
longopts = ['fast-open']
|
||||
else:
|
||||
shortopts = 'hs:p:k:m:c:t:vq'
|
||||
longopts = ['fast-open', 'workers:']
|
||||
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
|
||||
|
||||
if config_path:
|
||||
logging.info('loading config from %s' % config_path)
|
||||
with open(config_path, 'rb') as f:
|
||||
try:
|
||||
config = json.load(f, object_hook=_decode_dict)
|
||||
except ValueError as e:
|
||||
logging.error('found an error in config.json: %s',
|
||||
e.message)
|
||||
sys.exit(1)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
|
||||
v_count = 0
|
||||
for key, value in optlist:
|
||||
if key == '-p':
|
||||
config['server_port'] = int(value)
|
||||
elif key == '-k':
|
||||
config['password'] = value
|
||||
elif key == '-l':
|
||||
config['local_port'] = int(value)
|
||||
elif key == '-s':
|
||||
config['server'] = value
|
||||
elif key == '-m':
|
||||
config['method'] = value
|
||||
elif key == '-b':
|
||||
config['local_address'] = value
|
||||
elif key == '-v':
|
||||
v_count += 1
|
||||
# '-vv' turns on more verbose mode
|
||||
config['verbose'] = v_count
|
||||
elif key == '-t':
|
||||
config['timeout'] = int(value)
|
||||
elif key == '--fast-open':
|
||||
config['fast_open'] = True
|
||||
elif key == '--workers':
|
||||
config['workers'] = value
|
||||
elif key == '-h':
|
||||
if is_local:
|
||||
print_local_help()
|
||||
else:
|
||||
print_server_help()
|
||||
sys.exit(0)
|
||||
elif key == '-q':
|
||||
v_count -= 1
|
||||
config['verbose'] = v_count
|
||||
except getopt.GetoptError as e:
|
||||
print >>sys.stderr, e
|
||||
print_help(is_local)
|
||||
sys.exit(2)
|
||||
|
||||
if not config:
|
||||
logging.error('config not specified')
|
||||
print_help(is_local)
|
||||
sys.exit(2)
|
||||
|
||||
config['password'] = config.get('password', None)
|
||||
config['method'] = config.get('method', 'aes-256-cfb')
|
||||
config['port_password'] = config.get('port_password', None)
|
||||
config['timeout'] = int(config.get('timeout', 300))
|
||||
config['fast_open'] = config.get('fast_open', False)
|
||||
config['workers'] = config.get('workers', 1)
|
||||
config['verbose'] = config.get('verbose', False)
|
||||
config['local_address'] = config.get('local_address', '127.0.0.1')
|
||||
config['local_port'] = config.get('local_port', 1080)
|
||||
if is_local:
|
||||
if config.get('server', None) is None:
|
||||
logging.error('server addr not specified')
|
||||
print_local_help()
|
||||
sys.exit(2)
|
||||
else:
|
||||
config['server'] = config.get('server', '0.0.0.0')
|
||||
config['server_port'] = config.get('server_port', 8388)
|
||||
|
||||
if not ('password' in config and config['password']):
|
||||
logging.error('password not specified')
|
||||
print_help(is_local)
|
||||
sys.exit(2)
|
||||
|
||||
logging.getLogger('').handlers = []
|
||||
logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE')
|
||||
if config['verbose'] >= 2:
|
||||
level = VERBOSE_LEVEL
|
||||
elif config['verbose'] == 1:
|
||||
level = logging.DEBUG
|
||||
elif config['verbose'] == -1:
|
||||
level = logging.WARN
|
||||
elif config['verbose'] <= -2:
|
||||
level = logging.ERROR
|
||||
else:
|
||||
level = logging.INFO
|
||||
logging.basicConfig(level=level,
|
||||
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S', filemode='a+')
|
||||
|
||||
check_config(config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def print_help(is_local):
|
||||
if is_local:
|
||||
print_local_help()
|
||||
else:
|
||||
print_server_help()
|
||||
|
||||
|
||||
def print_local_help():
|
||||
print '''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT]
|
||||
[-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD]
|
||||
[-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] [-q]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-s SERVER_ADDR server address
|
||||
-p SERVER_PORT server port, default: 8388
|
||||
-b LOCAL_ADDR local binding address, default: 127.0.0.1
|
||||
-l LOCAL_PORT local port, default: 1080
|
||||
-k PASSWORD password
|
||||
-m METHOD encryption method, default: aes-256-cfb
|
||||
-t TIMEOUT timeout in seconds, default: 300
|
||||
-c CONFIG path to config file
|
||||
--fast-open use TCP_FASTOPEN, requires Linux 3.7+
|
||||
-v, -vv verbose mode
|
||||
-q, -qq quiet mode, only show warnings/errors
|
||||
|
||||
Online help: <https://github.com/clowwindy/shadowsocks>
|
||||
'''
|
||||
|
||||
|
||||
def print_server_help():
|
||||
print '''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD
|
||||
-m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open]
|
||||
[--workers WORKERS] [-v] [-q]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-s SERVER_ADDR server address, default: 0.0.0.0
|
||||
-p SERVER_PORT server port, default: 8388
|
||||
-k PASSWORD password
|
||||
-m METHOD encryption method, default: aes-256-cfb
|
||||
-t TIMEOUT timeout in seconds, default: 300
|
||||
-c CONFIG path to config file
|
||||
--fast-open use TCP_FASTOPEN, requires Linux 3.7+
|
||||
--workers WORKERS number of workers, available on Unix/Linux
|
||||
-v, -vv verbose mode
|
||||
-q, -qq quiet mode, only show warnings/errors
|
||||
|
||||
Online help: <https://github.com/clowwindy/shadowsocks>
|
||||
'''
|
||||
|
||||
|
||||
def _decode_list(data):
|
||||
rv = []
|
||||
for item in data:
|
||||
if isinstance(item, unicode):
|
||||
item = item.encode('utf-8')
|
||||
elif isinstance(item, list):
|
||||
item = _decode_list(item)
|
||||
elif isinstance(item, dict):
|
||||
item = _decode_dict(item)
|
||||
rv.append(item)
|
||||
return rv
|
||||
|
||||
|
||||
def _decode_dict(data):
|
||||
rv = {}
|
||||
for key, value in data.iteritems():
|
||||
if isinstance(key, unicode):
|
||||
key = key.encode('utf-8')
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf-8')
|
||||
elif isinstance(value, list):
|
||||
value = _decode_list(value)
|
||||
elif isinstance(value, dict):
|
||||
value = _decode_dict(value)
|
||||
rv[key] = value
|
||||
return rv
|
2
switchrule.py
Normal file
2
switchrule.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
def isTurnOn(plan, switch):
|
||||
return True
|
Loading…
Add table
Add a link
Reference in a new issue