From c738ebb1289840d0eb342df244fb42c2f846f442 Mon Sep 17 00:00:00 2001 From: bianzhifu Date: Sat, 27 Aug 2016 17:22:51 +0800 Subject: [PATCH 1/6] add manyuser --- shadowsocks/config.py | 22 ++++++ shadowsocks/dbtransfer.py | 143 ++++++++++++++++++++++++++++++++++++ shadowsocks/manager.py | 46 ++++++++---- shadowsocks/servers.py | 37 ++++++++++ shadowsocks/shadowsocks.sql | 37 ++++++++++ 5 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 shadowsocks/config.py create mode 100644 shadowsocks/dbtransfer.py create mode 100755 shadowsocks/servers.py create mode 100644 shadowsocks/shadowsocks.sql diff --git a/shadowsocks/config.py b/shadowsocks/config.py new file mode 100644 index 0000000..bf159e1 --- /dev/null +++ b/shadowsocks/config.py @@ -0,0 +1,22 @@ +import logging + +#Config +MYSQL_HOST = '127.0.0.1' +MYSQL_PORT = 3306 +MYSQL_USER = 'root' +MYSQL_PASS = '' +MYSQL_DB = 'shadowsocks' + +SS_BIND_IP = '0.0.0.0' +SS_METHOD = 'rc4-md5' +MANAGE_BIND_IP = '127.0.0.1' +MANAGE_PORT = 3333 + +CHECKTIME = 15 +SYNCTIME = 600 + +#LOG CONFIG +LOG_ENABLE = False +LOG_LEVEL = logging.INFO +LOG_FILE = '/val/log/shadowsocks.log' + diff --git a/shadowsocks/dbtransfer.py b/shadowsocks/dbtransfer.py new file mode 100644 index 0000000..f82365a --- /dev/null +++ b/shadowsocks/dbtransfer.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import logging +import cymysql +import time +import socket +import config +import json + + +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 + @staticmethod + def get_mysql_conn(): + 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') + return conn; + @staticmethod + def send_command(cmd): + data = '' + try: + cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + cli.settimeout(1) + cli.sendto(cmd, ('%s' % (config.MANAGE_BIND_IP), config.MANAGE_PORT)) + data, addr = cli.recvfrom(1500) + cli.close() + # TODO: bad way solve timed out + # time.sleep(0.05) + except: + logging.warn('send_command response') + return data + + @staticmethod + def get_servers_transfer(): + dt_transfer = {} + cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + cli.settimeout(2) + cli.sendto('transfer: {}', ('%s' % (config.MANAGE_BIND_IP), config.MANAGE_PORT)) + while True: + data, addr = cli.recvfrom(1500) + if data == 'e': + break + data = json.loads(data) + dt_transfer.update(data) + cli.close() + return dt_transfer + + def push_db_all_user(self): + logging.info("push_db_all_user") + dt_transfer = self.get_servers_transfer() + last_time = time.time() + conn = DbTransfer.get_mysql_conn() + cur = conn.cursor() + for port in dt_transfer.keys(): + update_sql='UPDATE user '+\ + 'set b_usage=b_usage+'+str(dt_transfer[port])+\ + ' where ss_port = '+str(port) + insert_sql='insert bandwidth_log value(null,"node1",'+str(port)+','+str(dt_transfer[port])+','+str(int(last_time))+')' + logging.info(update_sql) + cur.execute(update_sql) + cur.execute(insert_sql) + cur.close() + conn.commit() + conn.close() + + @staticmethod + def pull_db_all_user(): + conn = DbTransfer.get_mysql_conn() + cur = conn.cursor() + cur.execute("SELECT ss_port, ss_pwd, b_usage, b_max, u_status 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(rows): + for row in rows: + server = json.loads(DbTransfer.get_instance().send_command('stat: {"server_port":%s}' % row[0])) + if server['stat'] != 'ko': + if row[4] < 0: + # stop disable or switch off user + logging.info('db stop server at port [%s] reason: disable' % (row[0])) + DbTransfer.send_command('remove: {"server_port":%s}' % row[0]) + elif row[2] >= row[3]: + # stop out bandwidth user + logging.info('db stop server at port [%s] reason: out bandwidth' % (row[0])) + DbTransfer.send_command('remove: {"server_port":%s}' % row[0]) + if server['password'] != row[1]: + # password changed + logging.info('db stop server at port [%s] reason: password changed' % (row[0])) + DbTransfer.send_command('remove: {"server_port":%s}' % row[0]) + else: + if row[4] > 0 and row[2] < row[3]: + logging.info('db start server at port [%s] pass [%s]' % (row[0], row[1])) + DbTransfer.send_command('add: {"server_port": %s, "password":"%s"}' % (row[0], row[1])) + + @staticmethod + def thread_db(): + import socket + import time + timeout = 30 + socket.setdefaulttimeout(timeout) + while True: + logging.info('db thread_db') + try: + rows = DbTransfer.get_instance().pull_db_all_user() + DbTransfer.del_server_out_of_bound_safe(rows) + except Exception as e: + import traceback + traceback.print_exc() + logging.warn('db thread except:%s' % e) + finally: + time.sleep(config.CHECKTIME) + + @staticmethod + def thread_push(): + import socket + import time + timeout = 30 + socket.setdefaulttimeout(timeout) + while True: + logging.info('db thread_push') + try: + DbTransfer.get_instance().push_db_all_user() + except Exception as e: + import traceback + traceback.print_exc() + logging.warn('db thread except:%s' % e) + finally: + time.sleep(config.SYNCTIME) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index b42ffa9..608081c 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -67,7 +67,7 @@ class Manager(object): exit(1) self._loop.add(self._control_socket, eventloop.POLL_IN, self) - self._loop.add_periodic(self.handle_periodic) + # self._loop.add_periodic(self.handle_periodic) port_password = config['port_password'] del config['port_password'] @@ -106,6 +106,14 @@ class Manager(object): logging.error("server not exist at %s:%d" % (config['server'], port)) + def stat_port(self, config): + port = int(config['server_port']) + servers = self._relays.get(port, None) + if servers: + self._send_control_data(b'{"stat":"ok", "password":"%s"}' % servers[0]._config['password']) + else: + self._send_control_data(b'{"stat":"ko"}') + def handle_event(self, sock, fd, event): if sock == self._control_socket and event == eventloop.POLL_IN: data, self._control_client_addr = sock.recvfrom(BUF_SIZE) @@ -113,22 +121,27 @@ class Manager(object): if parsed: command, config = parsed a_config = self._config.copy() - if config: - # let the command override the configuration file - a_config.update(config) - if 'server_port' not in a_config: - logging.error('can not find server_port in config') + if command == 'transfer': + self.handle_periodic() else: - if command == 'add': - self.add_port(a_config) - self._send_control_data(b'ok') - elif command == 'remove': - self.remove_port(a_config) - self._send_control_data(b'ok') - elif command == 'ping': - self._send_control_data(b'pong') + if config: + # let the command override the configuration file + a_config.update(config) + if 'server_port' not in a_config: + logging.error('can not find server_port in config') else: - logging.error('unknown command %s', command) + if command == 'add': + self.add_port(a_config) + self._send_control_data(b'ok') + elif command == 'remove': + self.remove_port(a_config) + self._send_control_data(b'ok') + elif command == 'stat': + self.stat_port(a_config) + elif command == 'ping': + self._send_control_data(b'pong') + else: + logging.error('unknown command %s', command) def _parse_command(self, data): # commands: @@ -158,7 +171,7 @@ class Manager(object): # use compact JSON format (without space) data = common.to_bytes(json.dumps(data_dict, separators=(',', ':'))) - self._send_control_data(b'stat: ' + data) + self._send_control_data(data) for k, v in self._statistics.items(): r[k] = v @@ -170,6 +183,7 @@ class Manager(object): i = 0 if len(r) > 0: send_data(r) + self._send_control_data('e') self._statistics.clear() def _send_control_data(self, data): diff --git a/shadowsocks/servers.py b/shadowsocks/servers.py new file mode 100755 index 0000000..49ad7ba --- /dev/null +++ b/shadowsocks/servers.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import thread +import time +import manager +import config +from dbtransfer import DbTransfer +if config.LOG_ENABLE: + logging.basicConfig( + filename=config.LOG_FILE, + level=config.LOG_LEVEL, + datefmt='%Y-%m-%d %H:%M:%S', + format='%(asctime)s %(levelname)s %(filename)s[%(lineno)d] %(message)s' + ) +def main(): + configer = { + 'server': '%s' % config.SS_BIND_IP, + 'local_port': 1081, + 'port_password': { + }, + 'method': '%s' % config.SS_METHOD, + 'manager_address': '%s:%s' % (config.MANAGE_BIND_IP, config.MANAGE_PORT), + 'timeout': 60, # some protocol keepalive packet 3 min Eg bt + 'fast_open': False, + 'verbose': 1 + } + start_shadowsock = thread.start_new_thread(manager.run, (configer,)) + time.sleep(1) + sync_users = thread.start_new_thread(DbTransfer.thread_db, ()) + time.sleep(1) + sysc_transfer = thread.start_new_thread(DbTransfer.thread_push, ()) + while True: + time.sleep(3600) +if __name__ == '__main__': + main() diff --git a/shadowsocks/shadowsocks.sql b/shadowsocks/shadowsocks.sql new file mode 100644 index 0000000..dbd8b60 --- /dev/null +++ b/shadowsocks/shadowsocks.sql @@ -0,0 +1,37 @@ + +SET NAMES utf8; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for `bandwidth_log` +-- ---------------------------- +DROP TABLE IF EXISTS `bandwidth_log`; +CREATE TABLE `bandwidth_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `node` varchar(255) NOT NULL, + `port` int(11) NOT NULL, + `data` bigint(20) NOT NULL, + `time` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for `user` +-- ---------------------------- +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `u_email` varchar(32) NOT NULL COMMENT '邮件', + `u_pwd` varchar(32) NOT NULL COMMENT '密码', + `u_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '用户状态', + `ss_port` int(11) NOT NULL COMMENT 'ss端口', + `ss_pwd` varchar(32) NOT NULL COMMENT 'ss密码', + `b_usage` bigint(20) NOT NULL COMMENT '使用流量', + `b_max` bigint(20) NOT NULL COMMENT '可使用最大流量', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `pk_ss_port` (`ss_port`) USING HASH, + UNIQUE KEY `pk_u_email` (`u_email`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=422 DEFAULT CHARSET=utf8; + +SET FOREIGN_KEY_CHECKS = 1; From 15a7a72c99ffc68432b985a8e650b05b07e276af Mon Sep 17 00:00:00 2001 From: bianzhifu Date: Sat, 27 Aug 2016 17:36:19 +0800 Subject: [PATCH 2/6] =?UTF-8?q?node=E8=8A=82=E7=82=B9=E5=8F=AF=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shadowsocks/config.py | 1 + shadowsocks/dbtransfer.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/config.py b/shadowsocks/config.py index bf159e1..641c9fd 100644 --- a/shadowsocks/config.py +++ b/shadowsocks/config.py @@ -12,6 +12,7 @@ SS_METHOD = 'rc4-md5' MANAGE_BIND_IP = '127.0.0.1' MANAGE_PORT = 3333 +NODE = 'node1' CHECKTIME = 15 SYNCTIME = 600 diff --git a/shadowsocks/dbtransfer.py b/shadowsocks/dbtransfer.py index f82365a..61304af 100644 --- a/shadowsocks/dbtransfer.py +++ b/shadowsocks/dbtransfer.py @@ -65,7 +65,7 @@ class DbTransfer(object): update_sql='UPDATE user '+\ 'set b_usage=b_usage+'+str(dt_transfer[port])+\ ' where ss_port = '+str(port) - insert_sql='insert bandwidth_log value(null,"node1",'+str(port)+','+str(dt_transfer[port])+','+str(int(last_time))+')' + insert_sql='insert bandwidth_log value(null,"'+config.NODE+'",'+str(port)+','+str(dt_transfer[port])+','+str(int(last_time))+')' logging.info(update_sql) cur.execute(update_sql) cur.execute(insert_sql) From 907a081d525b6589752fadb79ddfece5e0eb9e93 Mon Sep 17 00:00:00 2001 From: bianzhifu Date: Sat, 27 Aug 2016 18:42:32 +0800 Subject: [PATCH 3/6] fix logfile --- shadowsocks/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/config.py b/shadowsocks/config.py index 641c9fd..d18f014 100644 --- a/shadowsocks/config.py +++ b/shadowsocks/config.py @@ -19,5 +19,5 @@ SYNCTIME = 600 #LOG CONFIG LOG_ENABLE = False LOG_LEVEL = logging.INFO -LOG_FILE = '/val/log/shadowsocks.log' +LOG_FILE = '/var/log/shadowsocks.log' From 3b32ab42b21d8b44c0f1d68c7552dd7102f164ec Mon Sep 17 00:00:00 2001 From: bianzhifu Date: Sat, 27 Aug 2016 19:11:47 +0800 Subject: [PATCH 4/6] add nickname --- shadowsocks/shadowsocks.sql | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/shadowsocks/shadowsocks.sql b/shadowsocks/shadowsocks.sql index dbd8b60..4ec0937 100644 --- a/shadowsocks/shadowsocks.sql +++ b/shadowsocks/shadowsocks.sql @@ -13,7 +13,7 @@ CREATE TABLE `bandwidth_log` ( `data` bigint(20) NOT NULL, `time` int(11) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for `user` @@ -23,6 +23,7 @@ CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `u_email` varchar(32) NOT NULL COMMENT '邮件', `u_pwd` varchar(32) NOT NULL COMMENT '密码', + `u_nickname` varchar(255) NOT NULL COMMENT '昵称', `u_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '用户状态', `ss_port` int(11) NOT NULL COMMENT 'ss端口', `ss_pwd` varchar(32) NOT NULL COMMENT 'ss密码', @@ -30,8 +31,8 @@ CREATE TABLE `user` ( `b_max` bigint(20) NOT NULL COMMENT '可使用最大流量', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `pk_ss_port` (`ss_port`) USING HASH, - UNIQUE KEY `pk_u_email` (`u_email`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=422 DEFAULT CHARSET=utf8; + UNIQUE KEY `pk_ss_port` (`ss_port`), + UNIQUE KEY `pk_u_email` (`u_email`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT; SET FOREIGN_KEY_CHECKS = 1; From 5d94686ac3f8ce9b2770bbed3ef4b2c9b9cda5f6 Mon Sep 17 00:00:00 2001 From: bianzhifu Date: Mon, 29 Aug 2016 18:50:28 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0web=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shadowsocks/api.py | 78 ++++++++++++++++++++++++++++++++ shadowsocks/templates/index.html | 33 ++++++++++++++ shadowsocks/templates/login.html | 58 ++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 shadowsocks/api.py create mode 100644 shadowsocks/templates/index.html create mode 100644 shadowsocks/templates/login.html diff --git a/shadowsocks/api.py b/shadowsocks/api.py new file mode 100644 index 0000000..5888a9a --- /dev/null +++ b/shadowsocks/api.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import config +import logging +import cymysql +import re +from flask import Flask,request,session,redirect,render_template +if config.LOG_ENABLE: + logging.basicConfig( + filename=config.LOG_FILE, + level=config.LOG_LEVEL, + datefmt='%Y-%m-%d %H:%M:%S', + format='%(asctime)s %(levelname)s %(filename)s[%(lineno)d] %(message)s' + ) +app = Flask(__name__) +app.secret_key = 'asdjhlasdlkjahskldhakjshd782934879123987*(Z&*(&98237498' + +def get_mysql_conn(): + 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') + return conn; +def validateEmail(email): + if len(email) > 4: + if re.match("\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", email) != None: + return True + return False +@app.route("/") +def index(): + if 'uid' in session: + conn = get_mysql_conn() + cur = conn.cursor(cursor=cymysql.cursors.DictCursor) + cur.execute("SELECT * FROM user WHERE id=%s",(session['uid'])) + userinfo = cur.fetchone() + cur.close() + conn.close() + return render_template("index.html",user=userinfo) + return redirect("/login") +@app.route("/login",methods=['POST', 'GET']) +def login(): + if request.method == 'POST': + email = request.form["username"] + password = request.form["password"] + if(validateEmail(email)): + conn = get_mysql_conn() + cur = conn.cursor(cursor=cymysql.cursors.DictCursor) + cur.execute("SELECT * FROM user WHERE u_email=%s",(email)) + userinfo = cur.fetchone() + cur.close() + conn.close() + if(userinfo == None or userinfo["u_pwd"] != password): + return render_template("login.html", errormsg=u"用户不存在或者密码错误") + else: + session['uid']=userinfo['id'] + return redirect("/") + else: + return render_template("login.html", errormsg=u"邮件格式错误") + else: + return render_template("login.html") +@app.route('/logout') +def logout(): + session.pop('uid', None) + return redirect("/login") +@app.route('/update_ss_pwd',methods=['POST']) +def update_ss_pwd(): + if 'uid' in session: + ss_pwd = request.form["ss_pwd"] + conn = get_mysql_conn() + cur = conn.cursor(cursor=cymysql.cursors.DictCursor) + update_sql = "update user set ss_pwd=%s where id=%s" + cur.execute(update_sql,(ss_pwd,session['uid'])) + cur.close() + conn.close() + return u"修改成功" + else: + return u"修改失败" +if __name__ == "__main__": + app.debug=True + app.run() diff --git a/shadowsocks/templates/index.html b/shadowsocks/templates/index.html new file mode 100644 index 0000000..f8a4af9 --- /dev/null +++ b/shadowsocks/templates/index.html @@ -0,0 +1,33 @@ + + + + + + SS查询系统 + + + + +
+
+

Welcome {{ user.u_nickname }}

+

IP:107.182.191.61

+

加密方式:rc4-md5

+

端口:{{ user.ss_port }}

+

密码:{{ user.ss_pwd }}

+

已用流量:

+

总流量:

+
+
+ + \ No newline at end of file diff --git a/shadowsocks/templates/login.html b/shadowsocks/templates/login.html new file mode 100644 index 0000000..0540243 --- /dev/null +++ b/shadowsocks/templates/login.html @@ -0,0 +1,58 @@ + + + + + + SS查询系统 + + + +
+
+
+
+
+
SS查询系统
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ 登录 +
+
+
+
+ + {% if errormsg %} +
+
+
+
错误信息
+
{{ errormsg }}
+
+ 确定 +
+
+
+ {% endif %} + + \ No newline at end of file From 049ca8487a679c271abc056dc4e0a38a8eb32427 Mon Sep 17 00:00:00 2001 From: bianzhifu Date: Mon, 29 Aug 2016 18:54:04 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E7=9B=91=E5=90=AC=E5=A4=96=E7=BD=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shadowsocks/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shadowsocks/api.py b/shadowsocks/api.py index 5888a9a..3d84973 100644 --- a/shadowsocks/api.py +++ b/shadowsocks/api.py @@ -74,5 +74,4 @@ def update_ss_pwd(): else: return u"修改失败" if __name__ == "__main__": - app.debug=True - app.run() + app.run(host='0.0.0.0')