Frist Version
This commit is contained in:
5
Server/API/config/configuration.php
Normal file
5
Server/API/config/configuration.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
define('DB_NAME', ''); //数据库名
|
||||||
|
define('DB_USER', ''); //数据库账户
|
||||||
|
define('DB_PASS', ''); //数据库密码
|
||||||
|
define('DB_HOST', 'localhost'); //数据库主机
|
||||||
391
Server/API/config/function.php
Normal file
391
Server/API/config/function.php
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
<?php
|
||||||
|
//加载依赖
|
||||||
|
require_once 'configuration.php';
|
||||||
|
|
||||||
|
//获取端口号
|
||||||
|
function shadowsocks_port(){
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$port = mysql_query("SELECT port FROM user order by port desc limit 1");
|
||||||
|
$port = mysql_fetch_assoc($port);
|
||||||
|
$port = $port['port'] + 1;
|
||||||
|
if($port > 65535){
|
||||||
|
$result = "Error";
|
||||||
|
}else{
|
||||||
|
$result = $port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//新建账户,传入密码和流量值(MB)
|
||||||
|
function shadowsocks_create($pid,$passwd,$traffic){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}elseif(empty($passwd)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined password value.'
|
||||||
|
);
|
||||||
|
}elseif(empty($traffic)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined traffic value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
if(shadowsocks_port() != "Error"){
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$port = shadowsocks_port();
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_assoc($check);
|
||||||
|
if($check != ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Account already exists.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$traffic = $traffic * 1048576; //按照 GB 为流量单位
|
||||||
|
$create = mysql_query("INSERT INTO user(pid,passwd,port,transfer_enable) VALUES ('".$pid."','".$passwd."','".$port."','".$traffic."')");
|
||||||
|
if(!$create){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => $port
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Port exceeds the maximum value.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//暂停账户,传入端口号
|
||||||
|
function shadowsocks_suspend($pid){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_assoc($check);
|
||||||
|
if($check == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$passwd = md5(time().rand(0,100));
|
||||||
|
$suspend = mysql_query("UPDATE user SET passwd='".$passwd."' WHERE pid='".$pid."'");
|
||||||
|
if(!$suspend){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => 'Account successfully suspend.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//解除暂停,传入密码和端口号
|
||||||
|
function shadowsocks_unsuspend($pid,$passwd){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}elseif(empty($passwd)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined password value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_assoc($check);
|
||||||
|
if($check == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$unsuspend = mysql_query("UPDATE user SET passwd='".$passwd."' WHERE pid='".$pid."'");
|
||||||
|
if(!$unsuspend){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => 'Account successfully unsuspend.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//终止账户,传入端口号
|
||||||
|
function shadowsocks_terminate($pid){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_assoc($check);
|
||||||
|
if($check == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$terminate = mysql_query("DELETE FROM user WHERE pid='".$pid."'");
|
||||||
|
if(!$terminate){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => 'Account successfully terminated.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//修改套餐,传入流量值和端口号(MB)
|
||||||
|
function shadowsocks_changepackage($pid,$traffic){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}elseif(empty($traffic)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined traffic value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$traffic = $traffic * 1048576;
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_assoc($check);
|
||||||
|
if($check == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$changepackage = mysql_query("UPDATE user SET transfer_enable='".$traffic."' WHERE pid='".$pid."'");
|
||||||
|
if(!$changepackage){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => 'Account successfully modified.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//更改密码,传入密码和端口号
|
||||||
|
function shadowsocks_changepassword($pid,$passwd){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}elseif(empty($passwd)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined password value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_assoc($check);
|
||||||
|
if($check == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$changepassword = mysql_query("UPDATE user SET passwd='".$passwd."' WHERE pid='".$pid."'");
|
||||||
|
if(!$changepassword){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => 'Password reset complete.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//重置流量,传入端口号
|
||||||
|
function shadowsocks_reset($pid){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$check = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$check = mysql_fetch_array($check);
|
||||||
|
if($check == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$reset = mysql_query("UPDATE user SET u='0',d='0' WHERE pid='".$pid."'");
|
||||||
|
if(!$reset){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'MySQL query failed.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Success',
|
||||||
|
'result' => 'Account Reset success.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询账户,传入端口号
|
||||||
|
function shadowsocks_query($pid){
|
||||||
|
if(empty($pid)){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined product id value.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
$query = mysql_query("SELECT * FROM user WHERE pid='".$pid."'");
|
||||||
|
$query = mysql_fetch_assoc($query);
|
||||||
|
if($query == ""){
|
||||||
|
$result = array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'No data found.'
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$result = array(
|
||||||
|
'port' => $query['port'],
|
||||||
|
'upload' => $query['u'],
|
||||||
|
'download' => $query['d'],
|
||||||
|
'last_time' => $query['t']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
14
Server/API/cron.php
Normal file
14
Server/API/cron.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
//加载依赖
|
||||||
|
require_once 'config/configuration.php';
|
||||||
|
|
||||||
|
$mysql = mysql_connect(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
if(!$mysql) {
|
||||||
|
die(json_encode(array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Unable to connect to database.'
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
mysql_select_db(DB_NAME, $mysql);
|
||||||
|
mysql_query("UPDATE `user` SET `u` = '0', `d` = '0';");
|
||||||
|
}
|
||||||
1
Server/API/crontab.txt
Normal file
1
Server/API/crontab.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0 0 1 * * php -q /home/wwwroot/api.hhh.com/cron.php
|
||||||
BIN
Server/API/shadowsocks.sql.gz
Normal file
BIN
Server/API/shadowsocks.sql.gz
Normal file
Binary file not shown.
53
Server/API/shadowsocksapi.php
Normal file
53
Server/API/shadowsocksapi.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config/function.php';
|
||||||
|
|
||||||
|
if(empty($_POST['action'])){
|
||||||
|
die(json_encode(array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined value.'
|
||||||
|
)));
|
||||||
|
}else{
|
||||||
|
if(!empty($_POST['password'])){
|
||||||
|
$passwd = $_POST['password'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($_POST['traffic'])){
|
||||||
|
$traffic = $_POST['traffic'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($_POST['pid'])){
|
||||||
|
$pid = $_POST['pid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_POST['action'];
|
||||||
|
|
||||||
|
if($action == "create"){
|
||||||
|
$create = @shadowsocks_create($pid, $passwd, $traffic);
|
||||||
|
echo json_encode($create);
|
||||||
|
}elseif($action == "terminate"){
|
||||||
|
$terminate = @shadowsocks_terminate($pid);
|
||||||
|
echo json_encode($terminate);
|
||||||
|
}elseif($action == "suspend"){
|
||||||
|
$suspend = @shadowsocks_suspend($pid);
|
||||||
|
echo json_encode($suspend);
|
||||||
|
}elseif($action == "unsuspend"){
|
||||||
|
$unsuspend = @shadowsocks_unsuspend($pid, $passwd);
|
||||||
|
echo json_encode($unsuspend);
|
||||||
|
}elseif($action == "changepassword"){
|
||||||
|
$changepassword = @shadowsocks_changepassword($pid, $passwd);
|
||||||
|
echo json_encode($changepassword);
|
||||||
|
}elseif($action == "changepackage"){
|
||||||
|
$changepackage = @shadowsocks_changepackage($pid, $traffic);
|
||||||
|
echo json_encode($changepackage);
|
||||||
|
}elseif($action == "reset"){
|
||||||
|
$reset = @shadowsocks_reset($pid);
|
||||||
|
}elseif ($action == "query"){
|
||||||
|
$query = @shadowsocks_query($pid);
|
||||||
|
echo json_encode($query);
|
||||||
|
}else{
|
||||||
|
die(json_encode(array(
|
||||||
|
'status' => 'Error',
|
||||||
|
'result' => 'Undefined value.'
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Server/shadowsocks/Config.py
Executable file
12
Server/shadowsocks/Config.py
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#Config
|
||||||
|
MYSQL_HOST = ''
|
||||||
|
MYSQL_PORT = 3306
|
||||||
|
MYSQL_USER = ''
|
||||||
|
MYSQL_PASS = ''
|
||||||
|
MYSQL_DB = ''
|
||||||
|
|
||||||
|
MANAGE_PASS = '123456'
|
||||||
|
#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 = 8888
|
||||||
BIN
Server/shadowsocks/Config.pyc
Normal file
BIN
Server/shadowsocks/Config.pyc
Normal file
Binary file not shown.
1
Server/shadowsocks/__init__.py
Executable file
1
Server/shadowsocks/__init__.py
Executable file
@@ -0,0 +1 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
478
Server/shadowsocks/asyncdns.py
Executable file
478
Server/shadowsocks/asyncdns.py
Executable file
@@ -0,0 +1,478 @@
|
|||||||
|
#!/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
|
||||||
|
import common
|
||||||
|
import lru_cache
|
||||||
|
import eventloop
|
||||||
|
|
||||||
|
|
||||||
|
CACHE_SWEEP_INTERVAL = 30
|
||||||
|
|
||||||
|
VALID_HOSTNAME = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||||
|
|
||||||
|
common.patch_socket()
|
||||||
|
|
||||||
|
# rfc1035
|
||||||
|
# format
|
||||||
|
# +---------------------+
|
||||||
|
# | Header |
|
||||||
|
# +---------------------+
|
||||||
|
# | Question | the question for the name server
|
||||||
|
# +---------------------+
|
||||||
|
# | Answer | RRs answering the question
|
||||||
|
# +---------------------+
|
||||||
|
# | Authority | RRs pointing toward an authority
|
||||||
|
# +---------------------+
|
||||||
|
# | Additional | RRs holding additional information
|
||||||
|
# +---------------------+
|
||||||
|
#
|
||||||
|
# header
|
||||||
|
# 1 1 1 1 1 1
|
||||||
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | ID |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | QDCOUNT |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | ANCOUNT |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | NSCOUNT |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | ARCOUNT |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
|
||||||
|
QTYPE_ANY = 255
|
||||||
|
QTYPE_A = 1
|
||||||
|
QTYPE_AAAA = 28
|
||||||
|
QTYPE_CNAME = 5
|
||||||
|
QTYPE_NS = 2
|
||||||
|
QCLASS_IN = 1
|
||||||
|
|
||||||
|
|
||||||
|
def build_address(address):
|
||||||
|
address = address.strip('.')
|
||||||
|
labels = address.split('.')
|
||||||
|
results = []
|
||||||
|
for label in labels:
|
||||||
|
l = len(label)
|
||||||
|
if l > 63:
|
||||||
|
return None
|
||||||
|
results.append(chr(l))
|
||||||
|
results.append(label)
|
||||||
|
results.append('\0')
|
||||||
|
return ''.join(results)
|
||||||
|
|
||||||
|
|
||||||
|
def build_request(address, qtype, request_id):
|
||||||
|
header = struct.pack('!HBBHHHH', request_id, 1, 0, 1, 0, 0, 0)
|
||||||
|
addr = build_address(address)
|
||||||
|
qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN)
|
||||||
|
return header + addr + qtype_qclass
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ip(addrtype, data, length, offset):
|
||||||
|
if addrtype == QTYPE_A:
|
||||||
|
return socket.inet_ntop(socket.AF_INET, data[offset:offset + length])
|
||||||
|
elif addrtype == QTYPE_AAAA:
|
||||||
|
return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length])
|
||||||
|
elif addrtype in [QTYPE_CNAME, QTYPE_NS]:
|
||||||
|
return parse_name(data, offset)[1]
|
||||||
|
else:
|
||||||
|
return data[offset:offset + length]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_name(data, offset):
|
||||||
|
p = offset
|
||||||
|
labels = []
|
||||||
|
l = ord(data[p])
|
||||||
|
while l > 0:
|
||||||
|
if (l & (128 + 64)) == (128 + 64):
|
||||||
|
# pointer
|
||||||
|
pointer = struct.unpack('!H', data[p:p + 2])[0]
|
||||||
|
pointer &= 0x3FFF
|
||||||
|
r = parse_name(data, pointer)
|
||||||
|
labels.append(r[1])
|
||||||
|
p += 2
|
||||||
|
# pointer is the end
|
||||||
|
return p - offset, '.'.join(labels)
|
||||||
|
else:
|
||||||
|
labels.append(data[p + 1:p + 1 + l])
|
||||||
|
p += 1 + l
|
||||||
|
l = ord(data[p])
|
||||||
|
return p - offset + 1, '.'.join(labels)
|
||||||
|
|
||||||
|
|
||||||
|
# rfc1035
|
||||||
|
# record
|
||||||
|
# 1 1 1 1 1 1
|
||||||
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | |
|
||||||
|
# / /
|
||||||
|
# / NAME /
|
||||||
|
# | |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | TYPE |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | CLASS |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | TTL |
|
||||||
|
# | |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
# | RDLENGTH |
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||||
|
# / RDATA /
|
||||||
|
# / /
|
||||||
|
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
def parse_record(data, offset, question=False):
|
||||||
|
nlen, name = parse_name(data, offset)
|
||||||
|
if not question:
|
||||||
|
record_type, record_class, record_ttl, record_rdlength = struct.unpack(
|
||||||
|
'!HHiH', data[offset + nlen:offset + nlen + 10]
|
||||||
|
)
|
||||||
|
ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10)
|
||||||
|
return nlen + 10 + record_rdlength, \
|
||||||
|
(name, ip, record_type, record_class, record_ttl)
|
||||||
|
else:
|
||||||
|
record_type, record_class = struct.unpack(
|
||||||
|
'!HH', data[offset + nlen:offset + nlen + 4]
|
||||||
|
)
|
||||||
|
return nlen + 4, (name, None, record_type, record_class, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_header(data):
|
||||||
|
if len(data) >= 12:
|
||||||
|
header = struct.unpack('!HBBHHHH', data[:12])
|
||||||
|
res_id = header[0]
|
||||||
|
res_qr = header[1] & 128
|
||||||
|
res_tc = header[1] & 2
|
||||||
|
res_ra = header[2] & 128
|
||||||
|
res_rcode = header[2] & 15
|
||||||
|
# assert res_tc == 0
|
||||||
|
# assert res_rcode in [0, 3]
|
||||||
|
res_qdcount = header[3]
|
||||||
|
res_ancount = header[4]
|
||||||
|
res_nscount = header[5]
|
||||||
|
res_arcount = header[6]
|
||||||
|
return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount,
|
||||||
|
res_ancount, res_nscount, res_arcount)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_response(data):
|
||||||
|
try:
|
||||||
|
if len(data) >= 12:
|
||||||
|
header = parse_header(data)
|
||||||
|
if not header:
|
||||||
|
return None
|
||||||
|
res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \
|
||||||
|
res_ancount, res_nscount, res_arcount = header
|
||||||
|
|
||||||
|
qds = []
|
||||||
|
ans = []
|
||||||
|
offset = 12
|
||||||
|
for i in xrange(0, res_qdcount):
|
||||||
|
l, r = parse_record(data, offset, True)
|
||||||
|
offset += l
|
||||||
|
if r:
|
||||||
|
qds.append(r)
|
||||||
|
for i in xrange(0, res_ancount):
|
||||||
|
l, r = parse_record(data, offset)
|
||||||
|
offset += l
|
||||||
|
if r:
|
||||||
|
ans.append(r)
|
||||||
|
for i in xrange(0, res_nscount):
|
||||||
|
l, r = parse_record(data, offset)
|
||||||
|
offset += l
|
||||||
|
for i in xrange(0, res_arcount):
|
||||||
|
l, r = parse_record(data, offset)
|
||||||
|
offset += l
|
||||||
|
response = DNSResponse()
|
||||||
|
if qds:
|
||||||
|
response.hostname = qds[0][0]
|
||||||
|
for an in ans:
|
||||||
|
response.answers.append((an[1], an[2], an[3]))
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
logging.error(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_ip(address):
|
||||||
|
for family in (socket.AF_INET, socket.AF_INET6):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(family, address)
|
||||||
|
return family
|
||||||
|
except (TypeError, ValueError, OSError, IOError):
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_hostname(hostname):
|
||||||
|
if len(hostname) > 255:
|
||||||
|
return False
|
||||||
|
if hostname[-1] == ".":
|
||||||
|
hostname = hostname[:-1]
|
||||||
|
return all(VALID_HOSTNAME.match(x) for x in hostname.split("."))
|
||||||
|
|
||||||
|
|
||||||
|
class DNSResponse(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.hostname = None
|
||||||
|
self.answers = [] # each: (addr, type, class)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s: %s' % (self.hostname, str(self.answers))
|
||||||
|
|
||||||
|
|
||||||
|
STATUS_IPV4 = 0
|
||||||
|
STATUS_IPV6 = 1
|
||||||
|
|
||||||
|
|
||||||
|
class DNSResolver(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._cache = lru_cache.LRUCache(timeout=300)
|
||||||
|
self._last_time = time.time()
|
||||||
|
self._sock = None
|
||||||
|
self._servers = None
|
||||||
|
self._parse_resolv()
|
||||||
|
self._parse_hosts()
|
||||||
|
# TODO monitor hosts change and reload hosts
|
||||||
|
# TODO parse /etc/gai.conf and follow its rules
|
||||||
|
|
||||||
|
def _parse_resolv(self):
|
||||||
|
self._servers = []
|
||||||
|
try:
|
||||||
|
with open('/etc/resolv.conf', 'rb') as f:
|
||||||
|
content = f.readlines()
|
||||||
|
for line in content:
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
if line.startswith('nameserver'):
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
server = parts[1]
|
||||||
|
if is_ip(server) == socket.AF_INET:
|
||||||
|
self._servers.append(server)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
if not self._servers:
|
||||||
|
self._servers = ['8.8.4.4', '8.8.8.8']
|
||||||
|
|
||||||
|
def _parse_hosts(self):
|
||||||
|
etc_path = '/etc/hosts'
|
||||||
|
if os.environ.__contains__('WINDIR'):
|
||||||
|
etc_path = os.environ['WINDIR'] + '/system32/drivers/etc/hosts'
|
||||||
|
try:
|
||||||
|
with open(etc_path, 'rb') as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
line = line.strip()
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
ip = parts[0]
|
||||||
|
if is_ip(ip):
|
||||||
|
for i in xrange(1, len(parts)):
|
||||||
|
hostname = parts[i]
|
||||||
|
if hostname:
|
||||||
|
self._hosts[hostname] = ip
|
||||||
|
except IOError:
|
||||||
|
self._hosts['localhost'] = '127.0.0.1'
|
||||||
|
|
||||||
|
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.setblocking(False)
|
||||||
|
loop.add(self._sock, eventloop.POLL_IN)
|
||||||
|
loop.add_handler(self.handle_events)
|
||||||
|
|
||||||
|
def _call_callback(self, hostname, ip, error=None):
|
||||||
|
callbacks = self._hostname_to_cb.get(hostname, [])
|
||||||
|
for callback in callbacks:
|
||||||
|
if self._cb_to_hostname.__contains__(callback):
|
||||||
|
del self._cb_to_hostname[callback]
|
||||||
|
if ip or error:
|
||||||
|
callback((hostname, ip), error)
|
||||||
|
else:
|
||||||
|
callback((hostname, None),
|
||||||
|
Exception('unknown hostname %s' % hostname))
|
||||||
|
if self._hostname_to_cb.__contains__(hostname):
|
||||||
|
del self._hostname_to_cb[hostname]
|
||||||
|
if self._hostname_status.__contains__(hostname):
|
||||||
|
del self._hostname_status[hostname]
|
||||||
|
|
||||||
|
def _handle_data(self, data):
|
||||||
|
response = parse_response(data)
|
||||||
|
if response and response.hostname:
|
||||||
|
hostname = response.hostname
|
||||||
|
ip = None
|
||||||
|
for answer in response.answers:
|
||||||
|
if answer[1] in (QTYPE_A, QTYPE_AAAA) and \
|
||||||
|
answer[2] == QCLASS_IN:
|
||||||
|
ip = answer[0]
|
||||||
|
break
|
||||||
|
if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \
|
||||||
|
== STATUS_IPV4:
|
||||||
|
self._hostname_status[hostname] = STATUS_IPV6
|
||||||
|
self._send_req(hostname, QTYPE_AAAA)
|
||||||
|
else:
|
||||||
|
if ip:
|
||||||
|
self._cache[hostname] = ip
|
||||||
|
self._call_callback(hostname, ip)
|
||||||
|
|
||||||
|
def handle_events(self, events):
|
||||||
|
for sock, fd, event in events:
|
||||||
|
if sock != self._sock:
|
||||||
|
continue
|
||||||
|
if event & eventloop.POLL_ERR:
|
||||||
|
logging.error('dns 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:
|
||||||
|
data, addr = sock.recvfrom(1024)
|
||||||
|
if addr[0] not in self._servers:
|
||||||
|
logging.warn('received a packet other than our dns')
|
||||||
|
break
|
||||||
|
self._handle_data(data)
|
||||||
|
break
|
||||||
|
now = time.time()
|
||||||
|
if now - self._last_time > CACHE_SWEEP_INTERVAL:
|
||||||
|
self._cache.sweep()
|
||||||
|
self._last_time = now
|
||||||
|
|
||||||
|
def remove_callback(self, callback):
|
||||||
|
hostname = self._cb_to_hostname.get(callback)
|
||||||
|
if hostname:
|
||||||
|
del self._cb_to_hostname[callback]
|
||||||
|
arr = self._hostname_to_cb.get(hostname, None)
|
||||||
|
if arr:
|
||||||
|
arr.remove(callback)
|
||||||
|
if not arr:
|
||||||
|
del self._hostname_to_cb[hostname]
|
||||||
|
if self._hostname_status.__contains__(hostname):
|
||||||
|
del self._hostname_status[hostname]
|
||||||
|
|
||||||
|
def _send_req(self, hostname, qtype):
|
||||||
|
self._request_id += 1
|
||||||
|
if self._request_id > 32768:
|
||||||
|
self._request_id = 1
|
||||||
|
req = build_request(hostname, qtype, self._request_id)
|
||||||
|
for server in self._servers:
|
||||||
|
logging.debug('resolving %s with type %d using server %s',
|
||||||
|
hostname, qtype, server)
|
||||||
|
self._sock.sendto(req, (server, 53))
|
||||||
|
|
||||||
|
def resolve(self, hostname, callback):
|
||||||
|
if not hostname:
|
||||||
|
callback(None, Exception('empty hostname'))
|
||||||
|
elif is_ip(hostname):
|
||||||
|
callback((hostname, hostname), None)
|
||||||
|
elif self._hosts.__contains__(hostname):
|
||||||
|
logging.debug('hit hosts: %s', hostname)
|
||||||
|
ip = self._hosts[hostname]
|
||||||
|
callback((hostname, ip), None)
|
||||||
|
elif self._cache.__contains__(hostname):
|
||||||
|
logging.debug('hit cache: %s', hostname)
|
||||||
|
ip = self._cache[hostname]
|
||||||
|
callback((hostname, ip), None)
|
||||||
|
else:
|
||||||
|
if not is_valid_hostname(hostname):
|
||||||
|
callback(None, Exception('invalid hostname: %s' % hostname))
|
||||||
|
return
|
||||||
|
arr = self._hostname_to_cb.get(hostname, None)
|
||||||
|
if not arr:
|
||||||
|
self._hostname_status[hostname] = STATUS_IPV4
|
||||||
|
self._send_req(hostname, QTYPE_A)
|
||||||
|
self._hostname_to_cb[hostname] = [callback]
|
||||||
|
self._cb_to_hostname[callback] = hostname
|
||||||
|
else:
|
||||||
|
arr.append(callback)
|
||||||
|
# TODO send again only if waited too long
|
||||||
|
self._send_req(hostname, QTYPE_A)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._sock:
|
||||||
|
self._sock.close()
|
||||||
|
self._sock = None
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
logging.getLogger('').handlers = []
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S', filemode='a+')
|
||||||
|
|
||||||
|
def _callback(address, error):
|
||||||
|
print error, address
|
||||||
|
|
||||||
|
loop = eventloop.EventLoop()
|
||||||
|
resolver = DNSResolver()
|
||||||
|
resolver.add_to_loop(loop)
|
||||||
|
|
||||||
|
for hostname in ['www.google.com',
|
||||||
|
'8.8.8.8',
|
||||||
|
'localhost',
|
||||||
|
'activate.adobe.com',
|
||||||
|
'www.twitter.com',
|
||||||
|
'ipv6.google.com',
|
||||||
|
'ipv6.l.google.com',
|
||||||
|
'www.gmail.com',
|
||||||
|
'r4---sn-3qqp-ioql.googlevideo.com',
|
||||||
|
'www.baidu.com',
|
||||||
|
'www.a.shifen.com',
|
||||||
|
'm.baidu.jp',
|
||||||
|
'www.youku.com',
|
||||||
|
'www.twitter.com',
|
||||||
|
'ipv6.google.com']:
|
||||||
|
resolver.resolve(hostname, _callback)
|
||||||
|
|
||||||
|
loop.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
BIN
Server/shadowsocks/asyncdns.pyc
Normal file
BIN
Server/shadowsocks/asyncdns.pyc
Normal file
Binary file not shown.
93
Server/shadowsocks/asyncmgr.py
Executable file
93
Server/shadowsocks/asyncmgr.py
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/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
|
||||||
|
import common
|
||||||
|
import lru_cache
|
||||||
|
import eventloop
|
||||||
|
import server_pool
|
||||||
|
import Config
|
||||||
|
|
||||||
|
class ServerMgr(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._loop = None
|
||||||
|
self._sock = 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().cb_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()
|
||||||
BIN
Server/shadowsocks/asyncmgr.pyc
Normal file
BIN
Server/shadowsocks/asyncmgr.pyc
Normal file
Binary file not shown.
133
Server/shadowsocks/common.py
Executable file
133
Server/shadowsocks/common.py
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/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 socket
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def inet_ntop(family, ipstr):
|
||||||
|
if family == socket.AF_INET:
|
||||||
|
return socket.inet_ntoa(ipstr)
|
||||||
|
elif family == socket.AF_INET6:
|
||||||
|
v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j)))
|
||||||
|
for i, j in zip(ipstr[::2], ipstr[1::2]))
|
||||||
|
return v6addr
|
||||||
|
|
||||||
|
|
||||||
|
def inet_pton(family, addr):
|
||||||
|
if family == socket.AF_INET:
|
||||||
|
return socket.inet_aton(addr)
|
||||||
|
elif family == socket.AF_INET6:
|
||||||
|
if '.' in addr: # a v4 addr
|
||||||
|
v4addr = addr[addr.rindex(':') + 1:]
|
||||||
|
v4addr = socket.inet_aton(v4addr)
|
||||||
|
v4addr = map(lambda x: ('%02X' % ord(x)), v4addr)
|
||||||
|
v4addr.insert(2, ':')
|
||||||
|
newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr)
|
||||||
|
return inet_pton(family, newaddr)
|
||||||
|
dbyts = [0] * 8 # 8 groups
|
||||||
|
grps = addr.split(':')
|
||||||
|
for i, v in enumerate(grps):
|
||||||
|
if v:
|
||||||
|
dbyts[i] = int(v, 16)
|
||||||
|
else:
|
||||||
|
for j, w in enumerate(grps[::-1]):
|
||||||
|
if w:
|
||||||
|
dbyts[7 - j] = int(w, 16)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
break
|
||||||
|
return ''.join((chr(i // 256) + chr(i % 256)) for i in dbyts)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("What family?")
|
||||||
|
|
||||||
|
|
||||||
|
def patch_socket():
|
||||||
|
if not hasattr(socket, 'inet_pton'):
|
||||||
|
socket.inet_pton = inet_pton
|
||||||
|
|
||||||
|
if not hasattr(socket, 'inet_ntop'):
|
||||||
|
socket.inet_ntop = inet_ntop
|
||||||
|
|
||||||
|
|
||||||
|
patch_socket()
|
||||||
|
|
||||||
|
|
||||||
|
ADDRTYPE_IPV4 = 1
|
||||||
|
ADDRTYPE_IPV6 = 4
|
||||||
|
ADDRTYPE_HOST = 3
|
||||||
|
|
||||||
|
|
||||||
|
def pack_addr(address):
|
||||||
|
for family in (socket.AF_INET, socket.AF_INET6):
|
||||||
|
try:
|
||||||
|
r = socket.inet_pton(family, address)
|
||||||
|
if family == socket.AF_INET6:
|
||||||
|
return '\x04' + r
|
||||||
|
else:
|
||||||
|
return '\x01' + r
|
||||||
|
except (TypeError, ValueError, OSError, IOError):
|
||||||
|
pass
|
||||||
|
if len(address) > 255:
|
||||||
|
address = address[:255] # TODO
|
||||||
|
return '\x03' + chr(len(address)) + address
|
||||||
|
|
||||||
|
|
||||||
|
def parse_header(data):
|
||||||
|
addrtype = ord(data[0])
|
||||||
|
dest_addr = None
|
||||||
|
dest_port = None
|
||||||
|
header_length = 0
|
||||||
|
if addrtype == ADDRTYPE_IPV4:
|
||||||
|
if len(data) >= 7:
|
||||||
|
dest_addr = socket.inet_ntoa(data[1:5])
|
||||||
|
dest_port = struct.unpack('>H', data[5:7])[0]
|
||||||
|
header_length = 7
|
||||||
|
else:
|
||||||
|
logging.warn('header is too short')
|
||||||
|
elif addrtype == ADDRTYPE_HOST:
|
||||||
|
if len(data) > 2:
|
||||||
|
addrlen = ord(data[1])
|
||||||
|
if len(data) >= 2 + addrlen:
|
||||||
|
dest_addr = data[2:2 + addrlen]
|
||||||
|
dest_port = struct.unpack('>H', data[2 + addrlen:4 +
|
||||||
|
addrlen])[0]
|
||||||
|
header_length = 4 + addrlen
|
||||||
|
else:
|
||||||
|
logging.warn('header is too short')
|
||||||
|
else:
|
||||||
|
logging.warn('header is too short')
|
||||||
|
elif addrtype == ADDRTYPE_IPV6:
|
||||||
|
if len(data) >= 19:
|
||||||
|
dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17])
|
||||||
|
dest_port = struct.unpack('>H', data[17:19])[0]
|
||||||
|
header_length = 19
|
||||||
|
else:
|
||||||
|
logging.warn('header is too short')
|
||||||
|
else:
|
||||||
|
logging.warn('unsupported addrtype %d, maybe wrong password' %
|
||||||
|
addrtype)
|
||||||
|
if dest_addr is None:
|
||||||
|
return None
|
||||||
|
return addrtype, dest_addr, dest_port, header_length
|
||||||
BIN
Server/shadowsocks/common.pyc
Normal file
BIN
Server/shadowsocks/common.pyc
Normal file
Binary file not shown.
10
Server/shadowsocks/config.json
Executable file
10
Server/shadowsocks/config.json
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"server":"0.0.0.0",
|
||||||
|
"server_ipv6": "[::]",
|
||||||
|
"server_port":8889,
|
||||||
|
"local_address": "127.0.0.1",
|
||||||
|
"local_port":1080,
|
||||||
|
"password":"123456",
|
||||||
|
"timeout":300,
|
||||||
|
"method":"rc4-md5"
|
||||||
|
}
|
||||||
132
Server/shadowsocks/db_transfer.py
Executable file
132
Server/shadowsocks/db_transfer.py
Executable file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/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 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:
|
||||||
|
if ServerPool.get_instance().server_is_run(row[0]) is True:
|
||||||
|
if row[5] == 0 or row[6] == 0:
|
||||||
|
#stop disable or switch off user
|
||||||
|
logging.info('db stop server at port [%s] reason: disable' % (row[0]))
|
||||||
|
ServerPool.get_instance().del_server(row[0])
|
||||||
|
elif row[1] + row[2] >= row[3]:
|
||||||
|
#stop out bandwidth user
|
||||||
|
logging.info('db stop server at port [%s] reason: out bandwidth' % (row[0]))
|
||||||
|
ServerPool.get_instance().del_server(row[0])
|
||||||
|
if ServerPool.get_instance().tcp_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])
|
||||||
|
else:
|
||||||
|
if row[5] == 1 and row[6] == 1 and row[1] + row[2] < row[3]:
|
||||||
|
logging.info('db start server at port [%s] pass [%s]' % (row[0], row[4]))
|
||||||
|
ServerPool.get_instance().new_server(row[0], row[4])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def thread_db():
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
timeout = 60
|
||||||
|
socket.setdefaulttimeout(timeout)
|
||||||
|
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(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()
|
||||||
BIN
Server/shadowsocks/db_transfer.pyc
Normal file
BIN
Server/shadowsocks/db_transfer.pyc
Normal file
Binary file not shown.
233
Server/shadowsocks/encrypt.py
Executable file
233
Server/shadowsocks/encrypt.py
Executable file
@@ -0,0 +1,233 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
import string
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
import encrypt_salsa20
|
||||||
|
import encrypt_rc4_md5
|
||||||
|
|
||||||
|
|
||||||
|
def random_string(length):
|
||||||
|
try:
|
||||||
|
import M2Crypto.Rand
|
||||||
|
return M2Crypto.Rand.rand_bytes(length)
|
||||||
|
except ImportError:
|
||||||
|
# TODO really strong enough on Linux?
|
||||||
|
return os.urandom(length)
|
||||||
|
|
||||||
|
|
||||||
|
cached_tables = {}
|
||||||
|
cached_keys = {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_table(key):
|
||||||
|
m = hashlib.md5()
|
||||||
|
m.update(key)
|
||||||
|
s = m.digest()
|
||||||
|
(a, b) = struct.unpack('<QQ', s)
|
||||||
|
table = [c for c in string.maketrans('', '')]
|
||||||
|
for i in xrange(1, 1024):
|
||||||
|
table.sort(lambda x, y: int(a % (ord(x) + i) - a % (ord(y) + i)))
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def init_table(key, method=None):
|
||||||
|
if method is not None and method == 'table':
|
||||||
|
method = None
|
||||||
|
if method:
|
||||||
|
try:
|
||||||
|
__import__('M2Crypto')
|
||||||
|
except ImportError:
|
||||||
|
logging.error(('M2Crypto is required to use %s, please run'
|
||||||
|
' `apt-get install python-m2crypto`') % method)
|
||||||
|
sys.exit(1)
|
||||||
|
if not method:
|
||||||
|
if key in cached_tables:
|
||||||
|
return cached_tables[key]
|
||||||
|
encrypt_table = ''.join(get_table(key))
|
||||||
|
decrypt_table = string.maketrans(encrypt_table,
|
||||||
|
string.maketrans('', ''))
|
||||||
|
cached_tables[key] = [encrypt_table, decrypt_table]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
Encryptor(key, method) # test if the settings if OK
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
password = str(password)
|
||||||
|
r = cached_keys.get(password, None)
|
||||||
|
if r:
|
||||||
|
return r
|
||||||
|
m = []
|
||||||
|
i = 0
|
||||||
|
while len(''.join(m)) < (key_len + iv_len):
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
data = password
|
||||||
|
if i > 0:
|
||||||
|
data = m[i - 1] + password
|
||||||
|
md5.update(data)
|
||||||
|
m.append(md5.digest())
|
||||||
|
i += 1
|
||||||
|
ms = ''.join(m)
|
||||||
|
key = ms[:key_len]
|
||||||
|
iv = ms[key_len:key_len + iv_len]
|
||||||
|
cached_keys[password] = (key, iv)
|
||||||
|
return (key, iv)
|
||||||
|
|
||||||
|
|
||||||
|
method_supported = {
|
||||||
|
'aes-128-cfb': (16, 16),
|
||||||
|
'aes-192-cfb': (24, 16),
|
||||||
|
'aes-256-cfb': (32, 16),
|
||||||
|
'bf-cfb': (16, 8),
|
||||||
|
'camellia-128-cfb': (16, 16),
|
||||||
|
'camellia-192-cfb': (24, 16),
|
||||||
|
'camellia-256-cfb': (32, 16),
|
||||||
|
'cast5-cfb': (16, 8),
|
||||||
|
'des-cfb': (8, 8),
|
||||||
|
'idea-cfb': (16, 8),
|
||||||
|
'rc2-cfb': (16, 8),
|
||||||
|
'rc4': (16, 0),
|
||||||
|
'rc4-md5': (16, 16),
|
||||||
|
'seed-cfb': (16, 16),
|
||||||
|
'salsa20-ctr': (32, 8),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Encryptor(object):
|
||||||
|
def __init__(self, key, method=None):
|
||||||
|
if method == 'table':
|
||||||
|
method = None
|
||||||
|
self.key = key
|
||||||
|
self.method = method
|
||||||
|
self.iv = None
|
||||||
|
self.iv_sent = False
|
||||||
|
self.cipher_iv = ''
|
||||||
|
self.decipher = None
|
||||||
|
if method:
|
||||||
|
self.cipher = self.get_cipher(key, method, 1, iv=random_string(32))
|
||||||
|
else:
|
||||||
|
self.encrypt_table, self.decrypt_table = init_table(key)
|
||||||
|
self.cipher = None
|
||||||
|
|
||||||
|
def get_cipher_len(self, method):
|
||||||
|
method = method.lower()
|
||||||
|
m = method_supported.get(method, None)
|
||||||
|
return m
|
||||||
|
|
||||||
|
def iv_len(self):
|
||||||
|
return len(self.cipher_iv)
|
||||||
|
|
||||||
|
def get_cipher(self, password, method, op, iv=None):
|
||||||
|
password = password.encode('utf-8')
|
||||||
|
method = method.lower()
|
||||||
|
m = self.get_cipher_len(method)
|
||||||
|
if m:
|
||||||
|
key, iv_ = EVP_BytesToKey(password, m[0], m[1])
|
||||||
|
if iv is None:
|
||||||
|
iv = iv_
|
||||||
|
iv = iv[:m[1]]
|
||||||
|
if op == 1:
|
||||||
|
# this iv is for cipher not decipher
|
||||||
|
self.cipher_iv = iv[:m[1]]
|
||||||
|
if method == 'salsa20-ctr':
|
||||||
|
return encrypt_salsa20.Salsa20Cipher(method, key, iv, op)
|
||||||
|
elif method == 'rc4-md5':
|
||||||
|
return encrypt_rc4_md5.create_cipher(method, key, iv, op)
|
||||||
|
else:
|
||||||
|
import M2Crypto.EVP
|
||||||
|
return M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv,
|
||||||
|
op, key_as_bytes=0, d='md5',
|
||||||
|
salt=None, i=1, padding=1)
|
||||||
|
|
||||||
|
logging.error('method %s not supported' % method)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def encrypt(self, buf):
|
||||||
|
if len(buf) == 0:
|
||||||
|
return buf
|
||||||
|
if not self.method:
|
||||||
|
return string.translate(buf, self.encrypt_table)
|
||||||
|
else:
|
||||||
|
if self.iv_sent:
|
||||||
|
return self.cipher.update(buf)
|
||||||
|
else:
|
||||||
|
self.iv_sent = True
|
||||||
|
return self.cipher_iv + self.cipher.update(buf)
|
||||||
|
|
||||||
|
def decrypt(self, buf):
|
||||||
|
if len(buf) == 0:
|
||||||
|
return buf
|
||||||
|
if not self.method:
|
||||||
|
return string.translate(buf, self.decrypt_table)
|
||||||
|
else:
|
||||||
|
if self.decipher is None:
|
||||||
|
decipher_iv_len = self.get_cipher_len(self.method)[1]
|
||||||
|
decipher_iv = buf[:decipher_iv_len]
|
||||||
|
self.decipher = self.get_cipher(self.key, self.method, 0,
|
||||||
|
iv=decipher_iv)
|
||||||
|
buf = buf[decipher_iv_len:]
|
||||||
|
if len(buf) == 0:
|
||||||
|
return buf
|
||||||
|
return self.decipher.update(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_all(password, method, op, data):
|
||||||
|
if method is not None and method.lower() == 'table':
|
||||||
|
method = None
|
||||||
|
if not method:
|
||||||
|
[encrypt_table, decrypt_table] = init_table(password)
|
||||||
|
if op:
|
||||||
|
return string.translate(data, encrypt_table)
|
||||||
|
else:
|
||||||
|
return string.translate(data, decrypt_table)
|
||||||
|
else:
|
||||||
|
import M2Crypto.EVP
|
||||||
|
result = []
|
||||||
|
method = method.lower()
|
||||||
|
(key_len, iv_len) = method_supported[method]
|
||||||
|
(key, _) = EVP_BytesToKey(password, key_len, iv_len)
|
||||||
|
if op:
|
||||||
|
iv = random_string(iv_len)
|
||||||
|
result.append(iv)
|
||||||
|
else:
|
||||||
|
iv = data[:iv_len]
|
||||||
|
data = data[iv_len:]
|
||||||
|
if method == 'salsa20-ctr':
|
||||||
|
cipher = encrypt_salsa20.Salsa20Cipher(method, key, iv, op)
|
||||||
|
elif method == 'rc4-md5':
|
||||||
|
cipher = encrypt_rc4_md5.create_cipher(method, key, iv, op)
|
||||||
|
else:
|
||||||
|
cipher = M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv,
|
||||||
|
op, key_as_bytes=0, d='md5',
|
||||||
|
salt=None, i=1, padding=1)
|
||||||
|
result.append(cipher.update(data))
|
||||||
|
return ''.join(result)
|
||||||
BIN
Server/shadowsocks/encrypt.pyc
Normal file
BIN
Server/shadowsocks/encrypt.pyc
Normal file
Binary file not shown.
35
Server/shadowsocks/encrypt_rc4_md5.py
Executable file
35
Server/shadowsocks/encrypt_rc4_md5.py
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None,
|
||||||
|
i=1, padding=1):
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
md5.update(key)
|
||||||
|
md5.update(iv)
|
||||||
|
rc4_key = md5.digest()
|
||||||
|
|
||||||
|
import M2Crypto.EVP
|
||||||
|
return M2Crypto.EVP.Cipher('rc4', rc4_key, '', op, key_as_bytes=0,
|
||||||
|
d='md5', salt=None, i=1, padding=1)
|
||||||
BIN
Server/shadowsocks/encrypt_rc4_md5.pyc
Normal file
BIN
Server/shadowsocks/encrypt_rc4_md5.pyc
Normal file
Binary file not shown.
153
Server/shadowsocks/encrypt_salsa20.py
Executable file
153
Server/shadowsocks/encrypt_salsa20.py
Executable file
@@ -0,0 +1,153 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
import time
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
slow_xor = False
|
||||||
|
imported = False
|
||||||
|
|
||||||
|
BLOCK_SIZE = 16384
|
||||||
|
|
||||||
|
|
||||||
|
def run_imports():
|
||||||
|
global imported, slow_xor, salsa20, numpy
|
||||||
|
if not imported:
|
||||||
|
imported = True
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except ImportError:
|
||||||
|
logging.error('can not import numpy, using SLOW XOR')
|
||||||
|
logging.error('please install numpy if you use salsa20')
|
||||||
|
slow_xor = True
|
||||||
|
try:
|
||||||
|
import salsa20
|
||||||
|
except ImportError:
|
||||||
|
logging.error('you have to install salsa20 before you use salsa20')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def numpy_xor(a, b):
|
||||||
|
if slow_xor:
|
||||||
|
return py_xor_str(a, b)
|
||||||
|
dtype = numpy.byte
|
||||||
|
if len(a) % 4 == 0:
|
||||||
|
dtype = numpy.uint32
|
||||||
|
elif len(a) % 2 == 0:
|
||||||
|
dtype = numpy.uint16
|
||||||
|
|
||||||
|
ab = numpy.frombuffer(a, dtype=dtype)
|
||||||
|
bb = numpy.frombuffer(b, dtype=dtype)
|
||||||
|
c = numpy.bitwise_xor(ab, bb)
|
||||||
|
r = c.tostring()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def py_xor_str(a, b):
|
||||||
|
c = []
|
||||||
|
for i in xrange(0, len(a)):
|
||||||
|
c.append(chr(ord(a[i]) ^ ord(b[i])))
|
||||||
|
return ''.join(c)
|
||||||
|
|
||||||
|
|
||||||
|
class Salsa20Cipher(object):
|
||||||
|
"""a salsa20 CTR implemetation, provides m2crypto like cipher API"""
|
||||||
|
|
||||||
|
def __init__(self, alg, key, iv, op, key_as_bytes=0, d=None, salt=None,
|
||||||
|
i=1, padding=1):
|
||||||
|
run_imports()
|
||||||
|
if alg != 'salsa20-ctr':
|
||||||
|
raise Exception('unknown algorithm')
|
||||||
|
self._key = key
|
||||||
|
self._nonce = struct.unpack('<Q', iv)[0]
|
||||||
|
self._pos = 0
|
||||||
|
self._next_stream()
|
||||||
|
|
||||||
|
def _next_stream(self):
|
||||||
|
self._nonce &= 0xFFFFFFFFFFFFFFFF
|
||||||
|
self._stream = salsa20.Salsa20_keystream(BLOCK_SIZE,
|
||||||
|
struct.pack('<Q',
|
||||||
|
self._nonce),
|
||||||
|
self._key)
|
||||||
|
self._nonce += 1
|
||||||
|
|
||||||
|
def update(self, data):
|
||||||
|
results = []
|
||||||
|
while True:
|
||||||
|
remain = BLOCK_SIZE - self._pos
|
||||||
|
cur_data = data[:remain]
|
||||||
|
cur_data_len = len(cur_data)
|
||||||
|
cur_stream = self._stream[self._pos:self._pos + cur_data_len]
|
||||||
|
self._pos = self._pos + cur_data_len
|
||||||
|
data = data[remain:]
|
||||||
|
|
||||||
|
results.append(numpy_xor(cur_data, cur_stream))
|
||||||
|
|
||||||
|
if self._pos >= BLOCK_SIZE:
|
||||||
|
self._next_stream()
|
||||||
|
self._pos = 0
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
return ''.join(results)
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
from os import urandom
|
||||||
|
import random
|
||||||
|
|
||||||
|
rounds = 1 * 1024
|
||||||
|
plain = urandom(BLOCK_SIZE * rounds)
|
||||||
|
import M2Crypto.EVP
|
||||||
|
# cipher = M2Crypto.EVP.Cipher('aes_128_cfb', 'k' * 32, 'i' * 16, 1,
|
||||||
|
# key_as_bytes=0, d='md5', salt=None, i=1,
|
||||||
|
# padding=1)
|
||||||
|
# decipher = M2Crypto.EVP.Cipher('aes_128_cfb', 'k' * 32, 'i' * 16, 0,
|
||||||
|
# key_as_bytes=0, d='md5', salt=None, i=1,
|
||||||
|
# padding=1)
|
||||||
|
|
||||||
|
cipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1)
|
||||||
|
decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1)
|
||||||
|
results = []
|
||||||
|
pos = 0
|
||||||
|
print 'salsa20 test start'
|
||||||
|
start = time.time()
|
||||||
|
while pos < len(plain):
|
||||||
|
l = random.randint(100, 32768)
|
||||||
|
c = cipher.update(plain[pos:pos + l])
|
||||||
|
results.append(c)
|
||||||
|
pos += l
|
||||||
|
pos = 0
|
||||||
|
c = ''.join(results)
|
||||||
|
results = []
|
||||||
|
while pos < len(plain):
|
||||||
|
l = random.randint(100, 32768)
|
||||||
|
results.append(decipher.update(c[pos:pos + l]))
|
||||||
|
pos += l
|
||||||
|
end = time.time()
|
||||||
|
print 'speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start))
|
||||||
|
assert ''.join(results) == plain
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
BIN
Server/shadowsocks/encrypt_salsa20.pyc
Normal file
BIN
Server/shadowsocks/encrypt_salsa20.pyc
Normal file
Binary file not shown.
244
Server/shadowsocks/eventloop.py
Executable file
244
Server/shadowsocks/eventloop.py
Executable file
@@ -0,0 +1,244 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# from ssloop
|
||||||
|
# https://github.com/clowwindy/ssloop
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import select
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR',
|
||||||
|
'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES']
|
||||||
|
|
||||||
|
POLL_NULL = 0x00
|
||||||
|
POLL_IN = 0x01
|
||||||
|
POLL_OUT = 0x04
|
||||||
|
POLL_ERR = 0x08
|
||||||
|
POLL_HUP = 0x10
|
||||||
|
POLL_NVAL = 0x20
|
||||||
|
|
||||||
|
|
||||||
|
EVENT_NAMES = {
|
||||||
|
POLL_NULL: 'POLL_NULL',
|
||||||
|
POLL_IN: 'POLL_IN',
|
||||||
|
POLL_OUT: 'POLL_OUT',
|
||||||
|
POLL_ERR: 'POLL_ERR',
|
||||||
|
POLL_HUP: 'POLL_HUP',
|
||||||
|
POLL_NVAL: 'POLL_NVAL',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EpollLoop(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._epoll = select.epoll()
|
||||||
|
|
||||||
|
def poll(self, timeout):
|
||||||
|
return self._epoll.poll(timeout)
|
||||||
|
|
||||||
|
def add_fd(self, fd, mode):
|
||||||
|
self._epoll.register(fd, mode)
|
||||||
|
|
||||||
|
def remove_fd(self, fd):
|
||||||
|
self._epoll.unregister(fd)
|
||||||
|
|
||||||
|
def modify_fd(self, fd, mode):
|
||||||
|
self._epoll.modify(fd, mode)
|
||||||
|
|
||||||
|
|
||||||
|
class KqueueLoop(object):
|
||||||
|
|
||||||
|
MAX_EVENTS = 1024
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._kqueue = select.kqueue()
|
||||||
|
self._fds = {}
|
||||||
|
|
||||||
|
def _control(self, fd, mode, flags):
|
||||||
|
events = []
|
||||||
|
if mode & POLL_IN:
|
||||||
|
events.append(select.kevent(fd, select.KQ_FILTER_READ, flags))
|
||||||
|
if mode & POLL_OUT:
|
||||||
|
events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags))
|
||||||
|
for e in events:
|
||||||
|
self._kqueue.control([e], 0)
|
||||||
|
|
||||||
|
def poll(self, timeout):
|
||||||
|
if timeout < 0:
|
||||||
|
timeout = None # kqueue behaviour
|
||||||
|
events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout)
|
||||||
|
results = defaultdict(lambda: POLL_NULL)
|
||||||
|
for e in events:
|
||||||
|
fd = e.ident
|
||||||
|
if e.filter == select.KQ_FILTER_READ:
|
||||||
|
results[fd] |= POLL_IN
|
||||||
|
elif e.filter == select.KQ_FILTER_WRITE:
|
||||||
|
results[fd] |= POLL_OUT
|
||||||
|
return results.iteritems()
|
||||||
|
|
||||||
|
def add_fd(self, fd, mode):
|
||||||
|
self._fds[fd] = mode
|
||||||
|
self._control(fd, mode, select.KQ_EV_ADD)
|
||||||
|
|
||||||
|
def remove_fd(self, fd):
|
||||||
|
self._control(fd, self._fds[fd], select.KQ_EV_DELETE)
|
||||||
|
del self._fds[fd]
|
||||||
|
|
||||||
|
def modify_fd(self, fd, mode):
|
||||||
|
self.remove_fd(fd)
|
||||||
|
self.add_fd(fd, mode)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectLoop(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._r_list = set()
|
||||||
|
self._w_list = set()
|
||||||
|
self._x_list = set()
|
||||||
|
|
||||||
|
def poll(self, timeout):
|
||||||
|
r, w, x = select.select(self._r_list, self._w_list, self._x_list,
|
||||||
|
timeout)
|
||||||
|
results = defaultdict(lambda: POLL_NULL)
|
||||||
|
for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]:
|
||||||
|
for fd in p[0]:
|
||||||
|
results[fd] |= p[1]
|
||||||
|
return results.items()
|
||||||
|
|
||||||
|
def add_fd(self, fd, mode):
|
||||||
|
if mode & POLL_IN:
|
||||||
|
self._r_list.add(fd)
|
||||||
|
if mode & POLL_OUT:
|
||||||
|
self._w_list.add(fd)
|
||||||
|
if mode & POLL_ERR:
|
||||||
|
self._x_list.add(fd)
|
||||||
|
|
||||||
|
def remove_fd(self, fd):
|
||||||
|
if fd in self._r_list:
|
||||||
|
self._r_list.remove(fd)
|
||||||
|
if fd in self._w_list:
|
||||||
|
self._w_list.remove(fd)
|
||||||
|
if fd in self._x_list:
|
||||||
|
self._x_list.remove(fd)
|
||||||
|
|
||||||
|
def modify_fd(self, fd, mode):
|
||||||
|
self.remove_fd(fd)
|
||||||
|
self.add_fd(fd, mode)
|
||||||
|
|
||||||
|
|
||||||
|
class EventLoop(object):
|
||||||
|
def __init__(self):
|
||||||
|
if hasattr(select, 'epoll'):
|
||||||
|
self._impl = EpollLoop()
|
||||||
|
model = 'epoll'
|
||||||
|
elif hasattr(select, 'kqueue'):
|
||||||
|
self._impl = KqueueLoop()
|
||||||
|
model = 'kqueue'
|
||||||
|
elif hasattr(select, 'select'):
|
||||||
|
self._impl = SelectLoop()
|
||||||
|
model = 'select'
|
||||||
|
else:
|
||||||
|
raise Exception('can not find any available functions in select '
|
||||||
|
'package')
|
||||||
|
self._fd_to_f = {}
|
||||||
|
self._handlers = []
|
||||||
|
self.stopping = False
|
||||||
|
logging.debug('using event model: %s', model)
|
||||||
|
|
||||||
|
def poll(self, timeout=None):
|
||||||
|
events = self._impl.poll(timeout)
|
||||||
|
return [(self._fd_to_f[fd], fd, event) for fd, event in events]
|
||||||
|
|
||||||
|
def add(self, f, mode):
|
||||||
|
fd = f.fileno()
|
||||||
|
self._fd_to_f[fd] = f
|
||||||
|
self._impl.add_fd(fd, mode)
|
||||||
|
|
||||||
|
def remove(self, f):
|
||||||
|
fd = f.fileno()
|
||||||
|
self._fd_to_f[fd] = None
|
||||||
|
self._impl.remove_fd(fd)
|
||||||
|
|
||||||
|
def modify(self, f, mode):
|
||||||
|
fd = f.fileno()
|
||||||
|
self._impl.modify_fd(fd, mode)
|
||||||
|
|
||||||
|
def add_handler(self, handler):
|
||||||
|
self._handlers.append(handler)
|
||||||
|
|
||||||
|
def remove_handler(self, handler):
|
||||||
|
self._handlers.remove(handler)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.stopping:
|
||||||
|
try:
|
||||||
|
events = self.poll(1)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
if errno_from_exception(e) == errno.EPIPE:
|
||||||
|
# Happens when the client closes the connection
|
||||||
|
logging.error('poll:%s', e)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logging.error('poll:%s', e)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
continue
|
||||||
|
for handler in self._handlers:
|
||||||
|
# TODO when there are a lot of handlers
|
||||||
|
try:
|
||||||
|
handler(events)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
logging.error(e)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# from tornado
|
||||||
|
def errno_from_exception(e):
|
||||||
|
"""Provides the errno from an Exception object.
|
||||||
|
|
||||||
|
There are cases that the errno attribute was not set so we pull
|
||||||
|
the errno out of the args but if someone instatiates an Exception
|
||||||
|
without any args you will get a tuple error. So this function
|
||||||
|
abstracts all that behavior to give you a safe way to get the
|
||||||
|
errno.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if hasattr(e, 'errno'):
|
||||||
|
return e.errno
|
||||||
|
elif e.args:
|
||||||
|
return e.args[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# from tornado
|
||||||
|
def get_sock_error(sock):
|
||||||
|
error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
|
||||||
|
return socket.error(error_number, os.strerror(error_number))
|
||||||
BIN
Server/shadowsocks/eventloop.pyc
Normal file
BIN
Server/shadowsocks/eventloop.pyc
Normal file
Binary file not shown.
19177
Server/shadowsocks/get-pip.py
Normal file
19177
Server/shadowsocks/get-pip.py
Normal file
File diff suppressed because it is too large
Load Diff
5
Server/shadowsocks/install.sh
Executable file
5
Server/shadowsocks/install.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
yum install m2crypto -y
|
||||||
|
|
||||||
|
python get-pip.py
|
||||||
|
pip install cymysql
|
||||||
69
Server/shadowsocks/local.py
Executable file
69
Server/shadowsocks/local.py
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/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 sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import utils
|
||||||
|
import encrypt
|
||||||
|
import eventloop
|
||||||
|
import tcprelay
|
||||||
|
import udprelay
|
||||||
|
import asyncdns
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
utils.check_python()
|
||||||
|
|
||||||
|
# fix py2exe
|
||||||
|
if hasattr(sys, "frozen") and sys.frozen in \
|
||||||
|
("windows_exe", "console_exe"):
|
||||||
|
p = os.path.dirname(os.path.abspath(sys.executable))
|
||||||
|
os.chdir(p)
|
||||||
|
|
||||||
|
config = utils.get_config(True)
|
||||||
|
|
||||||
|
utils.print_shadowsocks()
|
||||||
|
|
||||||
|
encrypt.init_table(config['password'], config['method'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
logging.info("starting local at %s:%d" %
|
||||||
|
(config['local_address'], config['local_port']))
|
||||||
|
|
||||||
|
dns_resolver = asyncdns.DNSResolver()
|
||||||
|
tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
|
||||||
|
udp_server = udprelay.UDPRelay(config, dns_resolver, True)
|
||||||
|
loop = eventloop.EventLoop()
|
||||||
|
dns_resolver.add_to_loop(loop)
|
||||||
|
tcp_server.add_to_loop(loop)
|
||||||
|
udp_server.add_to_loop(loop)
|
||||||
|
loop.run()
|
||||||
|
except (KeyboardInterrupt, IOError, OSError) as e:
|
||||||
|
logging.error(e)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
65
Server/shadowsocks/lru_cache.py
Executable file
65
Server/shadowsocks/lru_cache.py
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import logging
|
||||||
|
import heapq
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class LRUCache(collections.MutableMapping):
|
||||||
|
"""This class is not thread safe"""
|
||||||
|
|
||||||
|
def __init__(self, timeout=60, close_callback=None, *args, **kwargs):
|
||||||
|
self.timeout = timeout
|
||||||
|
self.close_callback = close_callback
|
||||||
|
self._store = {}
|
||||||
|
self._time_to_keys = collections.defaultdict(list)
|
||||||
|
self._last_visits = []
|
||||||
|
self.update(dict(*args, **kwargs)) # use the free update to set keys
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
# O(logm)
|
||||||
|
t = time.time()
|
||||||
|
self._time_to_keys[t].append(key)
|
||||||
|
heapq.heappush(self._last_visits, t)
|
||||||
|
return self._store[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# O(logm)
|
||||||
|
t = time.time()
|
||||||
|
self._store[key] = value
|
||||||
|
self._time_to_keys[t].append(key)
|
||||||
|
heapq.heappush(self._last_visits, t)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
# O(1)
|
||||||
|
del self._store[key]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._store)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._store)
|
||||||
|
|
||||||
|
def sweep(self):
|
||||||
|
# O(m)
|
||||||
|
now = time.time()
|
||||||
|
c = 0
|
||||||
|
while len(self._last_visits) > 0:
|
||||||
|
least = self._last_visits[0]
|
||||||
|
if now - least <= self.timeout:
|
||||||
|
break
|
||||||
|
if self.close_callback is not None:
|
||||||
|
for key in self._time_to_keys[least]:
|
||||||
|
if self._store.__contains__(key):
|
||||||
|
value = self._store[key]
|
||||||
|
self.close_callback(value)
|
||||||
|
for key in self._time_to_keys[least]:
|
||||||
|
heapq.heappop(self._last_visits)
|
||||||
|
if self._store.__contains__(key):
|
||||||
|
del self._store[key]
|
||||||
|
c += 1
|
||||||
|
del self._time_to_keys[least]
|
||||||
|
if c:
|
||||||
|
logging.debug('%d keys swept' % c)
|
||||||
BIN
Server/shadowsocks/lru_cache.pyc
Normal file
BIN
Server/shadowsocks/lru_cache.pyc
Normal file
Binary file not shown.
22
Server/shadowsocks/server.py
Executable file
22
Server/shadowsocks/server.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import thread
|
||||||
|
import server_pool
|
||||||
|
import db_transfer
|
||||||
|
|
||||||
|
#def test():
|
||||||
|
# thread.start_new_thread(DbTransfer.thread_db, ())
|
||||||
|
# Api.web_server()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
thread.start_new_thread(db_transfer.DbTransfer.thread_db, ())
|
||||||
|
"""
|
||||||
|
time.sleep(2)
|
||||||
|
server_pool.ServerPool.get_instance().new_server(3333, '2333')
|
||||||
|
while True:
|
||||||
|
server_pool.ServerPool.get_instance().new_server(2333, '2333')
|
||||||
|
server_pool.ServerPool.get_instance().del_server(2333)
|
||||||
|
time.sleep(0.01)
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
time.sleep(99999)
|
||||||
146
Server/shadowsocks/server_pool.py
Executable file
146
Server/shadowsocks/server_pool.py
Executable file
@@ -0,0 +1,146 @@
|
|||||||
|
#!/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 utils
|
||||||
|
import time
|
||||||
|
import eventloop
|
||||||
|
import tcprelay
|
||||||
|
import udprelay
|
||||||
|
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.tcp_servers_pool = {}
|
||||||
|
self.udp_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)
|
||||||
|
if port in self.tcp_servers_pool:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def new_server(self, port, password):
|
||||||
|
port = int(port)
|
||||||
|
logging.info("start server at %d" % port)
|
||||||
|
try:
|
||||||
|
udpsock = socket(AF_INET, SOCK_DGRAM)
|
||||||
|
udpsock.sendto('%s:%s:%s:1' % (Config.MANAGE_PASS, port, password), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
|
||||||
|
udpsock.close()
|
||||||
|
except Exception, e:
|
||||||
|
logging.warn(e)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def cb_new_server(self, port, password):
|
||||||
|
ret = True
|
||||||
|
port = int(port)
|
||||||
|
|
||||||
|
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[port] = tcp_server
|
||||||
|
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(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:
|
||||||
|
server = self.tcp_servers_pool[port]
|
||||||
|
del self.tcp_servers_pool[port]
|
||||||
|
server.destroy()
|
||||||
|
except Exception, e:
|
||||||
|
logging.warn(e)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_servers_transfer(self):
|
||||||
|
ret = {}
|
||||||
|
#this is different thread but safe
|
||||||
|
servers = self.tcp_servers_pool.copy()
|
||||||
|
for port in servers.keys():
|
||||||
|
ret[port] = [servers[port].server_transfer_ul, servers[port].server_transfer_dl]
|
||||||
|
return ret
|
||||||
BIN
Server/shadowsocks/server_pool.pyc
Normal file
BIN
Server/shadowsocks/server_pool.pyc
Normal file
Binary file not shown.
657
Server/shadowsocks/tcprelay.py
Executable file
657
Server/shadowsocks/tcprelay.py
Executable file
@@ -0,0 +1,657 @@
|
|||||||
|
#!/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 time
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
import random
|
||||||
|
import encrypt
|
||||||
|
import eventloop
|
||||||
|
import utils
|
||||||
|
from common import parse_header
|
||||||
|
|
||||||
|
|
||||||
|
TIMEOUTS_CLEAN_SIZE = 512
|
||||||
|
TIMEOUT_PRECISION = 4
|
||||||
|
|
||||||
|
MSG_FASTOPEN = 0x20000000
|
||||||
|
|
||||||
|
CMD_CONNECT = 1
|
||||||
|
CMD_BIND = 2
|
||||||
|
CMD_UDP_ASSOCIATE = 3
|
||||||
|
|
||||||
|
# local:
|
||||||
|
# stage 0 init
|
||||||
|
# stage 1 hello received, hello sent
|
||||||
|
# stage 2 UDP assoc
|
||||||
|
# stage 3 DNS
|
||||||
|
# stage 4 addr received, reply sent
|
||||||
|
# stage 5 remote connected
|
||||||
|
|
||||||
|
# remote:
|
||||||
|
# stage 0 init
|
||||||
|
# stage 3 DNS
|
||||||
|
# stage 4 addr received, reply sent
|
||||||
|
# stage 5 remote connected
|
||||||
|
|
||||||
|
STAGE_INIT = 0
|
||||||
|
STAGE_HELLO = 1
|
||||||
|
STAGE_UDP_ASSOC = 2
|
||||||
|
STAGE_DNS = 3
|
||||||
|
STAGE_REPLY = 4
|
||||||
|
STAGE_STREAM = 5
|
||||||
|
STAGE_DESTROYED = -1
|
||||||
|
|
||||||
|
# stream direction
|
||||||
|
STREAM_UP = 0
|
||||||
|
STREAM_DOWN = 1
|
||||||
|
|
||||||
|
# stream wait status
|
||||||
|
WAIT_STATUS_INIT = 0
|
||||||
|
WAIT_STATUS_READING = 1
|
||||||
|
WAIT_STATUS_WRITING = 2
|
||||||
|
WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING
|
||||||
|
|
||||||
|
BUF_SIZE = 32 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
class TCPRelayHandler(object):
|
||||||
|
def __init__(self, server, fd_to_handlers, loop, local_sock, config,
|
||||||
|
dns_resolver, is_local):
|
||||||
|
self._server = server
|
||||||
|
self._fd_to_handlers = fd_to_handlers
|
||||||
|
self._loop = loop
|
||||||
|
self._local_sock = local_sock
|
||||||
|
self._remote_sock = None
|
||||||
|
self._config = config
|
||||||
|
self._dns_resolver = dns_resolver
|
||||||
|
self._is_local = is_local
|
||||||
|
self._stage = STAGE_INIT
|
||||||
|
self._encryptor = encrypt.Encryptor(config['password'],
|
||||||
|
config['method'])
|
||||||
|
self._fastopen_connected = False
|
||||||
|
self._data_to_write_to_local = []
|
||||||
|
self._data_to_write_to_remote = []
|
||||||
|
self._upstream_status = WAIT_STATUS_READING
|
||||||
|
self._downstream_status = WAIT_STATUS_INIT
|
||||||
|
self._remote_address = None
|
||||||
|
if is_local:
|
||||||
|
self._chosen_server = self._get_a_server()
|
||||||
|
fd_to_handlers[local_sock.fileno()] = self
|
||||||
|
local_sock.setblocking(False)
|
||||||
|
local_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR)
|
||||||
|
self.last_activity = 0
|
||||||
|
self._update_activity()
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
# default __hash__ is id / 16
|
||||||
|
# we want to eliminate collisions
|
||||||
|
return id(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remote_address(self):
|
||||||
|
return self._remote_address
|
||||||
|
|
||||||
|
def _get_a_server(self):
|
||||||
|
server = self._config['server']
|
||||||
|
server_port = self._config['server_port']
|
||||||
|
if type(server_port) == list:
|
||||||
|
server_port = random.choice(server_port)
|
||||||
|
logging.debug('chosen server: %s:%d', server, server_port)
|
||||||
|
# TODO support multiple server IP
|
||||||
|
return server, server_port
|
||||||
|
|
||||||
|
def _update_activity(self):
|
||||||
|
self._server.update_activity(self)
|
||||||
|
|
||||||
|
def _update_stream(self, stream, status):
|
||||||
|
dirty = False
|
||||||
|
if stream == STREAM_DOWN:
|
||||||
|
if self._downstream_status != status:
|
||||||
|
self._downstream_status = status
|
||||||
|
dirty = True
|
||||||
|
elif stream == STREAM_UP:
|
||||||
|
if self._upstream_status != status:
|
||||||
|
self._upstream_status = status
|
||||||
|
dirty = True
|
||||||
|
if dirty:
|
||||||
|
if self._local_sock:
|
||||||
|
event = eventloop.POLL_ERR
|
||||||
|
if self._downstream_status & WAIT_STATUS_WRITING:
|
||||||
|
event |= eventloop.POLL_OUT
|
||||||
|
if self._upstream_status & WAIT_STATUS_READING:
|
||||||
|
event |= eventloop.POLL_IN
|
||||||
|
self._loop.modify(self._local_sock, event)
|
||||||
|
if self._remote_sock:
|
||||||
|
event = eventloop.POLL_ERR
|
||||||
|
if self._downstream_status & WAIT_STATUS_READING:
|
||||||
|
event |= eventloop.POLL_IN
|
||||||
|
if self._upstream_status & WAIT_STATUS_WRITING:
|
||||||
|
event |= eventloop.POLL_OUT
|
||||||
|
self._loop.modify(self._remote_sock, event)
|
||||||
|
|
||||||
|
def _write_to_sock(self, data, sock):
|
||||||
|
if not data or not sock:
|
||||||
|
return False
|
||||||
|
uncomplete = False
|
||||||
|
try:
|
||||||
|
l = len(data)
|
||||||
|
s = sock.send(data)
|
||||||
|
if s < l:
|
||||||
|
data = data[s:]
|
||||||
|
uncomplete = True
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
error_no = eventloop.errno_from_exception(e)
|
||||||
|
if error_no in (errno.EAGAIN, errno.EINPROGRESS,
|
||||||
|
errno.EWOULDBLOCK):
|
||||||
|
uncomplete = True
|
||||||
|
else:
|
||||||
|
logging.error(e)
|
||||||
|
if self._config['verbose']:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.destroy()
|
||||||
|
return False
|
||||||
|
if uncomplete:
|
||||||
|
if sock == self._local_sock:
|
||||||
|
self._data_to_write_to_local.append(data)
|
||||||
|
self._update_stream(STREAM_DOWN, WAIT_STATUS_WRITING)
|
||||||
|
elif sock == self._remote_sock:
|
||||||
|
self._data_to_write_to_remote.append(data)
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
|
||||||
|
else:
|
||||||
|
logging.error('write_all_to_sock:unknown socket')
|
||||||
|
else:
|
||||||
|
if sock == self._local_sock:
|
||||||
|
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
|
||||||
|
elif sock == self._remote_sock:
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_READING)
|
||||||
|
else:
|
||||||
|
logging.error('write_all_to_sock:unknown socket')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _handle_stage_reply(self, data):
|
||||||
|
if self._is_local:
|
||||||
|
data = self._encryptor.encrypt(data)
|
||||||
|
self._data_to_write_to_remote.append(data)
|
||||||
|
if self._is_local and not self._fastopen_connected and \
|
||||||
|
self._config['fast_open']:
|
||||||
|
try:
|
||||||
|
self._fastopen_connected = True
|
||||||
|
remote_sock = \
|
||||||
|
self._create_remote_socket(self._chosen_server[0],
|
||||||
|
self._chosen_server[1])
|
||||||
|
self._loop.add(remote_sock, eventloop.POLL_ERR)
|
||||||
|
data = ''.join(self._data_to_write_to_local)
|
||||||
|
l = len(data)
|
||||||
|
s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server)
|
||||||
|
if s < l:
|
||||||
|
data = data[s:]
|
||||||
|
self._data_to_write_to_local = [data]
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
|
||||||
|
else:
|
||||||
|
self._data_to_write_to_local = []
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_READING)
|
||||||
|
self._stage = STAGE_STREAM
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
if eventloop.errno_from_exception(e) == errno.EINPROGRESS:
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
|
||||||
|
elif eventloop.errno_from_exception(e) == errno.ENOTCONN:
|
||||||
|
logging.error('fast open not supported on this OS')
|
||||||
|
self._config['fast_open'] = False
|
||||||
|
self.destroy()
|
||||||
|
else:
|
||||||
|
logging.error(e)
|
||||||
|
if self._config['verbose']:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def _handle_stage_hello(self, data):
|
||||||
|
try:
|
||||||
|
if self._is_local:
|
||||||
|
cmd = ord(data[1])
|
||||||
|
if cmd == CMD_UDP_ASSOCIATE:
|
||||||
|
logging.debug('UDP associate')
|
||||||
|
if self._local_sock.family == socket.AF_INET6:
|
||||||
|
header = '\x05\x00\x00\x04'
|
||||||
|
else:
|
||||||
|
header = '\x05\x00\x00\x01'
|
||||||
|
addr, port = self._local_sock.getsockname()
|
||||||
|
addr_to_send = socket.inet_pton(self._local_sock.family,
|
||||||
|
addr)
|
||||||
|
port_to_send = struct.pack('>H', port)
|
||||||
|
self._write_to_sock(header + addr_to_send + port_to_send,
|
||||||
|
self._local_sock)
|
||||||
|
self._stage = STAGE_UDP_ASSOC
|
||||||
|
# just wait for the client to disconnect
|
||||||
|
return
|
||||||
|
elif cmd == CMD_CONNECT:
|
||||||
|
# just trim VER CMD RSV
|
||||||
|
data = data[3:]
|
||||||
|
else:
|
||||||
|
logging.error('unknown command %d', cmd)
|
||||||
|
self.destroy()
|
||||||
|
return
|
||||||
|
header_result = parse_header(data)
|
||||||
|
if header_result is None:
|
||||||
|
raise Exception('[%s]can not parse header' % (self._config['server_port']))
|
||||||
|
addrtype, remote_addr, remote_port, header_length = header_result
|
||||||
|
logging.info('connecting %s:%d' % (remote_addr, remote_port))
|
||||||
|
self._remote_address = (remote_addr, remote_port)
|
||||||
|
# pause reading
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
|
||||||
|
self._stage = STAGE_DNS
|
||||||
|
if self._is_local:
|
||||||
|
# forward address to remote
|
||||||
|
self._write_to_sock('\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10',
|
||||||
|
self._local_sock)
|
||||||
|
data_to_send = self._encryptor.encrypt(data)
|
||||||
|
self._data_to_write_to_remote.append(data_to_send)
|
||||||
|
# notice here may go into _handle_dns_resolved directly
|
||||||
|
self._dns_resolver.resolve(self._chosen_server[0],
|
||||||
|
self._handle_dns_resolved)
|
||||||
|
else:
|
||||||
|
if len(data) > header_length:
|
||||||
|
self._data_to_write_to_remote.append(data[header_length:])
|
||||||
|
# notice here may go into _handle_dns_resolved directly
|
||||||
|
self._dns_resolver.resolve(remote_addr,
|
||||||
|
self._handle_dns_resolved)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
if self._config['verbose']:
|
||||||
|
traceback.print_exc()
|
||||||
|
# TODO use logging when debug completed
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def _create_remote_socket(self, ip, port):
|
||||||
|
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]
|
||||||
|
remote_sock = socket.socket(af, socktype, proto)
|
||||||
|
self._remote_sock = remote_sock
|
||||||
|
self._fd_to_handlers[remote_sock.fileno()] = self
|
||||||
|
remote_sock.setblocking(False)
|
||||||
|
remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
return remote_sock
|
||||||
|
|
||||||
|
def _handle_dns_resolved(self, result, error):
|
||||||
|
if error:
|
||||||
|
logging.error(error)
|
||||||
|
self.destroy()
|
||||||
|
return
|
||||||
|
if result:
|
||||||
|
ip = result[1]
|
||||||
|
if ip:
|
||||||
|
try:
|
||||||
|
self._stage = STAGE_REPLY
|
||||||
|
remote_addr = ip
|
||||||
|
if self._is_local:
|
||||||
|
remote_port = self._chosen_server[1]
|
||||||
|
else:
|
||||||
|
remote_port = self._remote_address[1]
|
||||||
|
|
||||||
|
if self._is_local and self._config['fast_open']:
|
||||||
|
# wait for more data to arrive and send them in one SYN
|
||||||
|
self._stage = STAGE_REPLY
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_READING)
|
||||||
|
# TODO when there is already data in this packet
|
||||||
|
else:
|
||||||
|
remote_sock = self._create_remote_socket(remote_addr,
|
||||||
|
remote_port)
|
||||||
|
try:
|
||||||
|
remote_sock.connect((remote_addr, remote_port))
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
if eventloop.errno_from_exception(e) == \
|
||||||
|
errno.EINPROGRESS:
|
||||||
|
pass
|
||||||
|
self._loop.add(remote_sock,
|
||||||
|
eventloop.POLL_ERR | eventloop.POLL_OUT)
|
||||||
|
self._stage = STAGE_REPLY
|
||||||
|
self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
|
||||||
|
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
|
||||||
|
return
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
logging.error(e)
|
||||||
|
if self._config['verbose']:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def _on_local_read(self):
|
||||||
|
self._update_activity()
|
||||||
|
if not self._local_sock:
|
||||||
|
return
|
||||||
|
is_local = self._is_local
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
data = self._local_sock.recv(BUF_SIZE)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
if eventloop.errno_from_exception(e) in \
|
||||||
|
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
|
||||||
|
return
|
||||||
|
if not data:
|
||||||
|
self.destroy()
|
||||||
|
return
|
||||||
|
self._server.server_transfer_ul += len(data)
|
||||||
|
if not is_local:
|
||||||
|
data = self._encryptor.decrypt(data)
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
if self._stage == STAGE_STREAM:
|
||||||
|
if self._is_local:
|
||||||
|
data = self._encryptor.encrypt(data)
|
||||||
|
self._write_to_sock(data, self._remote_sock)
|
||||||
|
return
|
||||||
|
elif is_local and self._stage == STAGE_INIT:
|
||||||
|
# TODO check auth method
|
||||||
|
self._write_to_sock('\x05\00', self._local_sock)
|
||||||
|
self._stage = STAGE_HELLO
|
||||||
|
return
|
||||||
|
elif self._stage == STAGE_REPLY:
|
||||||
|
self._handle_stage_reply(data)
|
||||||
|
elif (is_local and self._stage == STAGE_HELLO) or \
|
||||||
|
(not is_local and self._stage == STAGE_INIT):
|
||||||
|
self._handle_stage_hello(data)
|
||||||
|
|
||||||
|
def _on_remote_read(self):
|
||||||
|
self._update_activity()
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
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):
|
||||||
|
return
|
||||||
|
if not data:
|
||||||
|
self.destroy()
|
||||||
|
return
|
||||||
|
self._server.server_transfer_dl += len(data)
|
||||||
|
if self._is_local:
|
||||||
|
data = self._encryptor.decrypt(data)
|
||||||
|
else:
|
||||||
|
data = self._encryptor.encrypt(data)
|
||||||
|
try:
|
||||||
|
self._write_to_sock(data, self._local_sock)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
if self._config['verbose']:
|
||||||
|
traceback.print_exc()
|
||||||
|
# TODO use logging when debug completed
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def _on_local_write(self):
|
||||||
|
if self._data_to_write_to_local:
|
||||||
|
data = ''.join(self._data_to_write_to_local)
|
||||||
|
self._data_to_write_to_local = []
|
||||||
|
self._write_to_sock(data, self._local_sock)
|
||||||
|
else:
|
||||||
|
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
|
||||||
|
|
||||||
|
def _on_remote_write(self):
|
||||||
|
self._stage = STAGE_STREAM
|
||||||
|
if self._data_to_write_to_remote:
|
||||||
|
data = ''.join(self._data_to_write_to_remote)
|
||||||
|
self._data_to_write_to_remote = []
|
||||||
|
self._write_to_sock(data, self._remote_sock)
|
||||||
|
else:
|
||||||
|
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))
|
||||||
|
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))
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def handle_event(self, sock, event):
|
||||||
|
if self._stage == STAGE_DESTROYED:
|
||||||
|
logging.debug('ignore handle_event: destroyed')
|
||||||
|
return
|
||||||
|
# order is important
|
||||||
|
if sock == self._remote_sock:
|
||||||
|
if event & eventloop.POLL_ERR:
|
||||||
|
self._on_remote_error()
|
||||||
|
if self._stage == STAGE_DESTROYED:
|
||||||
|
return
|
||||||
|
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
|
||||||
|
self._on_remote_read()
|
||||||
|
if self._stage == STAGE_DESTROYED:
|
||||||
|
return
|
||||||
|
if event & eventloop.POLL_OUT:
|
||||||
|
self._on_remote_write()
|
||||||
|
elif sock == self._local_sock:
|
||||||
|
if event & eventloop.POLL_ERR:
|
||||||
|
self._on_local_error()
|
||||||
|
if self._stage == STAGE_DESTROYED:
|
||||||
|
return
|
||||||
|
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
|
||||||
|
self._on_local_read()
|
||||||
|
if self._stage == STAGE_DESTROYED:
|
||||||
|
return
|
||||||
|
if event & eventloop.POLL_OUT:
|
||||||
|
self._on_local_write()
|
||||||
|
else:
|
||||||
|
logging.warn('unknown socket')
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
if self._stage == STAGE_DESTROYED:
|
||||||
|
logging.debug('already destroyed')
|
||||||
|
return
|
||||||
|
self._stage = STAGE_DESTROYED
|
||||||
|
if self._remote_sock:
|
||||||
|
try:
|
||||||
|
logging.debug('destroying remote')
|
||||||
|
self._loop.remove(self._remote_sock)
|
||||||
|
del self._fd_to_handlers[self._remote_sock.fileno()]
|
||||||
|
self._remote_sock.close()
|
||||||
|
self._remote_sock = None
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if self._local_sock:
|
||||||
|
try:
|
||||||
|
logging.debug('destroying local')
|
||||||
|
self._loop.remove(self._local_sock)
|
||||||
|
del self._fd_to_handlers[self._local_sock.fileno()]
|
||||||
|
self._local_sock.close()
|
||||||
|
self._local_sock = None
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self._dns_resolver.remove_callback(self._handle_dns_resolved)
|
||||||
|
self._server.remove_handler(self)
|
||||||
|
|
||||||
|
|
||||||
|
class TCPRelay(object):
|
||||||
|
def __init__(self, config, dns_resolver, is_local):
|
||||||
|
self._config = config
|
||||||
|
self._is_local = is_local
|
||||||
|
self._dns_resolver = dns_resolver
|
||||||
|
self._closed = False
|
||||||
|
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
|
||||||
|
# we trim the timeouts once a while
|
||||||
|
self._timeout_offset = 0 # last checked position for timeout
|
||||||
|
self._handler_to_timeouts = {} # key: handler value: index in timeouts
|
||||||
|
|
||||||
|
if is_local:
|
||||||
|
listen_addr = config['local_address']
|
||||||
|
listen_port = config['local_port']
|
||||||
|
else:
|
||||||
|
listen_addr = config['server']
|
||||||
|
listen_port = config['server_port']
|
||||||
|
|
||||||
|
addrs = socket.getaddrinfo(listen_addr, listen_port, 0,
|
||||||
|
socket.SOCK_STREAM, socket.SOL_TCP)
|
||||||
|
if len(addrs) == 0:
|
||||||
|
raise Exception("can't get addrinfo for %s:%d" %
|
||||||
|
(listen_addr, listen_port))
|
||||||
|
af, socktype, proto, canonname, sa = addrs[0]
|
||||||
|
server_socket = socket.socket(af, socktype, proto)
|
||||||
|
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
server_socket.bind(sa)
|
||||||
|
server_socket.setblocking(False)
|
||||||
|
if config['fast_open']:
|
||||||
|
try:
|
||||||
|
server_socket.setsockopt(socket.SOL_TCP, 23, 5)
|
||||||
|
except socket.error:
|
||||||
|
logging.error('warning: fast open is not available')
|
||||||
|
self._config['fast_open'] = False
|
||||||
|
server_socket.listen(1024)
|
||||||
|
self._server_socket = server_socket
|
||||||
|
|
||||||
|
def add_to_loop(self, loop):
|
||||||
|
if self._eventloop:
|
||||||
|
raise Exception('already add to loop')
|
||||||
|
if self._closed:
|
||||||
|
raise Exception('already closed')
|
||||||
|
self._eventloop = loop
|
||||||
|
loop.add_handler(self._handle_events)
|
||||||
|
|
||||||
|
self._eventloop.add(self._server_socket,
|
||||||
|
eventloop.POLL_IN | eventloop.POLL_ERR)
|
||||||
|
|
||||||
|
def remove_to_loop(self):
|
||||||
|
self._eventloop.remove(self._server_socket)
|
||||||
|
self._eventloop.remove_handler(self._handle_events)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
#destroy all conn
|
||||||
|
self.remove_to_loop()
|
||||||
|
for fd in self._fd_to_handlers.keys():
|
||||||
|
try:
|
||||||
|
self._fd_to_handlers[fd].destroy()
|
||||||
|
except Exception, e:
|
||||||
|
#already destroy
|
||||||
|
pass
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def remove_handler(self, handler):
|
||||||
|
index = self._handler_to_timeouts.get(hash(handler), -1)
|
||||||
|
if index >= 0:
|
||||||
|
# delete is O(n), so we just set it to None
|
||||||
|
self._timeouts[index] = None
|
||||||
|
del self._handler_to_timeouts[hash(handler)]
|
||||||
|
|
||||||
|
def update_activity(self, handler):
|
||||||
|
""" set handler to active """
|
||||||
|
now = int(time.time())
|
||||||
|
if now - handler.last_activity < TIMEOUT_PRECISION:
|
||||||
|
# thus we can lower timeout modification frequency
|
||||||
|
return
|
||||||
|
handler.last_activity = now
|
||||||
|
index = self._handler_to_timeouts.get(hash(handler), -1)
|
||||||
|
if index >= 0:
|
||||||
|
# delete is O(n), so we just set it to None
|
||||||
|
self._timeouts[index] = None
|
||||||
|
length = len(self._timeouts)
|
||||||
|
self._timeouts.append(handler)
|
||||||
|
self._handler_to_timeouts[hash(handler)] = length
|
||||||
|
|
||||||
|
def _sweep_timeout(self):
|
||||||
|
# tornado's timeout memory management is more flexible than we need
|
||||||
|
# we just need a sorted last_activity queue and it's faster than heapq
|
||||||
|
# in fact we can do O(1) insertion/remove so we invent our own
|
||||||
|
if self._timeouts:
|
||||||
|
logging.log(utils.VERBOSE_LEVEL, 'sweeping timeouts')
|
||||||
|
now = time.time()
|
||||||
|
length = len(self._timeouts)
|
||||||
|
pos = self._timeout_offset
|
||||||
|
while pos < length:
|
||||||
|
handler = self._timeouts[pos]
|
||||||
|
if handler:
|
||||||
|
if now - handler.last_activity < self._timeout:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if handler.remote_address:
|
||||||
|
logging.warn('timed out: %s:%d' %
|
||||||
|
handler.remote_address)
|
||||||
|
else:
|
||||||
|
logging.warn('timed out')
|
||||||
|
handler.destroy()
|
||||||
|
self._timeouts[pos] = None # free memory
|
||||||
|
pos += 1
|
||||||
|
else:
|
||||||
|
pos += 1
|
||||||
|
if pos > TIMEOUTS_CLEAN_SIZE and pos > length >> 1:
|
||||||
|
# clean up the timeout queue when it gets larger than half
|
||||||
|
# of the queue
|
||||||
|
self._timeouts = self._timeouts[pos:]
|
||||||
|
for key in self._handler_to_timeouts:
|
||||||
|
self._handler_to_timeouts[key] -= pos
|
||||||
|
pos = 0
|
||||||
|
self._timeout_offset = pos
|
||||||
|
|
||||||
|
def _handle_events(self, events):
|
||||||
|
for sock, fd, event in events:
|
||||||
|
if sock:
|
||||||
|
logging.log(utils.VERBOSE_LEVEL, 'fd %d %s', fd,
|
||||||
|
eventloop.EVENT_NAMES.get(event, event))
|
||||||
|
if sock == self._server_socket:
|
||||||
|
if event & eventloop.POLL_ERR:
|
||||||
|
# TODO
|
||||||
|
raise Exception('server_socket error')
|
||||||
|
try:
|
||||||
|
logging.debug('accept')
|
||||||
|
conn = self._server_socket.accept()
|
||||||
|
TCPRelayHandler(self, self._fd_to_handlers,
|
||||||
|
self._eventloop, conn[0], self._config,
|
||||||
|
self._dns_resolver, self._is_local)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
error_no = eventloop.errno_from_exception(e)
|
||||||
|
if error_no in (errno.EAGAIN, errno.EINPROGRESS,
|
||||||
|
errno.EWOULDBLOCK):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logging.error(e)
|
||||||
|
if self._config['verbose']:
|
||||||
|
traceback.print_exc()
|
||||||
|
else:
|
||||||
|
if sock:
|
||||||
|
handler = self._fd_to_handlers.get(fd, None)
|
||||||
|
if handler:
|
||||||
|
handler.handle_event(sock, event)
|
||||||
|
else:
|
||||||
|
logging.warn('poll removed fd')
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if now - self._last_time > TIMEOUT_PRECISION:
|
||||||
|
self._sweep_timeout()
|
||||||
|
self._last_time = now
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._closed = True
|
||||||
|
self._server_socket.close()
|
||||||
BIN
Server/shadowsocks/tcprelay.pyc
Normal file
BIN
Server/shadowsocks/tcprelay.pyc
Normal file
Binary file not shown.
10
Server/shadowsocks/udpmanagetest.py
Executable file
10
Server/shadowsocks/udpmanagetest.py
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import Config
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
|
udpsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||||
|
udpsock.sendto('%s:456789:123456:1' % (Config.MANAGE_PASS), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
|
||||||
|
time.sleep(20)
|
||||||
|
udpsock.sendto('%s:456789:123456:0' % (Config.MANAGE_PASS), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
|
||||||
282
Server/shadowsocks/udprelay.py
Executable file
282
Server/shadowsocks/udprelay.py
Executable file
@@ -0,0 +1,282 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# SOCKS5 UDP Request
|
||||||
|
# +----+------+------+----------+----------+----------+
|
||||||
|
# |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||||
|
# +----+------+------+----------+----------+----------+
|
||||||
|
# | 2 | 1 | 1 | Variable | 2 | Variable |
|
||||||
|
# +----+------+------+----------+----------+----------+
|
||||||
|
|
||||||
|
# SOCKS5 UDP Response
|
||||||
|
# +----+------+------+----------+----------+----------+
|
||||||
|
# |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||||
|
# +----+------+------+----------+----------+----------+
|
||||||
|
# | 2 | 1 | 1 | Variable | 2 | Variable |
|
||||||
|
# +----+------+------+----------+----------+----------+
|
||||||
|
|
||||||
|
# shadowsocks UDP Request (before encrypted)
|
||||||
|
# +------+----------+----------+----------+
|
||||||
|
# | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||||
|
# +------+----------+----------+----------+
|
||||||
|
# | 1 | Variable | 2 | Variable |
|
||||||
|
# +------+----------+----------+----------+
|
||||||
|
|
||||||
|
# shadowsocks UDP Response (before encrypted)
|
||||||
|
# +------+----------+----------+----------+
|
||||||
|
# | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||||
|
# +------+----------+----------+----------+
|
||||||
|
# | 1 | Variable | 2 | Variable |
|
||||||
|
# +------+----------+----------+----------+
|
||||||
|
|
||||||
|
# shadowsocks UDP Request and Response (after encrypted)
|
||||||
|
# +-------+--------------+
|
||||||
|
# | IV | PAYLOAD |
|
||||||
|
# +-------+--------------+
|
||||||
|
# | Fixed | Variable |
|
||||||
|
# +-------+--------------+
|
||||||
|
|
||||||
|
# HOW TO NAME THINGS
|
||||||
|
# ------------------
|
||||||
|
# `dest` means destination server, which is from DST fields in the SOCKS5
|
||||||
|
# request
|
||||||
|
# `local` means local server of shadowsocks
|
||||||
|
# `remote` means remote server of shadowsocks
|
||||||
|
# `client` means UDP clients that connects to other servers
|
||||||
|
# `server` means the UDP server that handles user requests
|
||||||
|
|
||||||
|
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
import errno
|
||||||
|
import random
|
||||||
|
import encrypt
|
||||||
|
import eventloop
|
||||||
|
import lru_cache
|
||||||
|
from common import parse_header, pack_addr
|
||||||
|
|
||||||
|
|
||||||
|
BUF_SIZE = 65536
|
||||||
|
|
||||||
|
|
||||||
|
def client_key(a, b, c, d):
|
||||||
|
return '%s:%s:%s:%s' % (a, b, c, d)
|
||||||
|
|
||||||
|
|
||||||
|
class UDPRelay(object):
|
||||||
|
def __init__(self, config, dns_resolver, is_local):
|
||||||
|
self._config = config
|
||||||
|
if is_local:
|
||||||
|
self._listen_addr = config['local_address']
|
||||||
|
self._listen_port = config['local_port']
|
||||||
|
self._remote_addr = config['server']
|
||||||
|
self._remote_port = config['server_port']
|
||||||
|
else:
|
||||||
|
self._listen_addr = config['server']
|
||||||
|
self._listen_port = config['server_port']
|
||||||
|
self._remote_addr = None
|
||||||
|
self._remote_port = None
|
||||||
|
self._dns_resolver = dns_resolver
|
||||||
|
self._password = config['password']
|
||||||
|
self._method = config['method']
|
||||||
|
self._timeout = config['timeout']
|
||||||
|
self._is_local = is_local
|
||||||
|
self._cache = lru_cache.LRUCache(timeout=config['timeout'],
|
||||||
|
close_callback=self._close_client)
|
||||||
|
self._client_fd_to_server_addr = \
|
||||||
|
lru_cache.LRUCache(timeout=config['timeout'])
|
||||||
|
self._eventloop = None
|
||||||
|
self._closed = False
|
||||||
|
self._last_time = time.time()
|
||||||
|
self._sockets = set()
|
||||||
|
|
||||||
|
addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0,
|
||||||
|
socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||||
|
if len(addrs) == 0:
|
||||||
|
raise Exception("can't get addrinfo for %s:%d" %
|
||||||
|
(self._listen_addr, self._listen_port))
|
||||||
|
af, socktype, proto, canonname, sa = addrs[0]
|
||||||
|
server_socket = socket.socket(af, socktype, proto)
|
||||||
|
server_socket.bind((self._listen_addr, self._listen_port))
|
||||||
|
server_socket.setblocking(False)
|
||||||
|
self._server_socket = server_socket
|
||||||
|
|
||||||
|
def _get_a_server(self):
|
||||||
|
server = self._config['server']
|
||||||
|
server_port = self._config['server_port']
|
||||||
|
if type(server_port) == list:
|
||||||
|
server_port = random.choice(server_port)
|
||||||
|
logging.debug('chosen server: %s:%d', server, server_port)
|
||||||
|
# TODO support multiple server IP
|
||||||
|
return server, server_port
|
||||||
|
|
||||||
|
def _close_client(self, client):
|
||||||
|
if hasattr(client, 'close'):
|
||||||
|
self._sockets.remove(client.fileno())
|
||||||
|
self._eventloop.remove(client)
|
||||||
|
client.close()
|
||||||
|
else:
|
||||||
|
# just an address
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _handle_server(self):
|
||||||
|
server = self._server_socket
|
||||||
|
data, r_addr = server.recvfrom(BUF_SIZE)
|
||||||
|
if not data:
|
||||||
|
logging.debug('UDP handle_server: data is empty')
|
||||||
|
if self._is_local:
|
||||||
|
frag = ord(data[2])
|
||||||
|
if frag != 0:
|
||||||
|
logging.warn('drop a message since frag is not 0')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
data = data[3:]
|
||||||
|
else:
|
||||||
|
data = encrypt.encrypt_all(self._password, self._method, 0, data)
|
||||||
|
# decrypt data
|
||||||
|
if not data:
|
||||||
|
logging.debug('UDP handle_server: data is empty after decrypt')
|
||||||
|
return
|
||||||
|
header_result = parse_header(data)
|
||||||
|
if header_result is None:
|
||||||
|
return
|
||||||
|
addrtype, dest_addr, dest_port, header_length = header_result
|
||||||
|
|
||||||
|
if self._is_local:
|
||||||
|
server_addr, server_port = self._get_a_server()
|
||||||
|
else:
|
||||||
|
server_addr, server_port = dest_addr, dest_port
|
||||||
|
|
||||||
|
key = client_key(r_addr[0], r_addr[1], dest_addr, dest_port)
|
||||||
|
client = self._cache.get(key, None)
|
||||||
|
if not client:
|
||||||
|
# TODO async getaddrinfo
|
||||||
|
addrs = socket.getaddrinfo(server_addr, server_port, 0,
|
||||||
|
socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||||
|
if addrs:
|
||||||
|
af, socktype, proto, canonname, sa = addrs[0]
|
||||||
|
client = socket.socket(af, socktype, proto)
|
||||||
|
client.setblocking(False)
|
||||||
|
self._cache[key] = client
|
||||||
|
self._client_fd_to_server_addr[client.fileno()] = r_addr
|
||||||
|
else:
|
||||||
|
# drop
|
||||||
|
return
|
||||||
|
self._sockets.add(client.fileno())
|
||||||
|
self._eventloop.add(client, eventloop.POLL_IN)
|
||||||
|
|
||||||
|
if self._is_local:
|
||||||
|
data = encrypt.encrypt_all(self._password, self._method, 1, data)
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
data = data[header_length:]
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
client.sendto(data, (server_addr, server_port))
|
||||||
|
except IOError as e:
|
||||||
|
err = eventloop.errno_from_exception(e)
|
||||||
|
if err in (errno.EINPROGRESS, errno.EAGAIN):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
def _handle_client(self, sock):
|
||||||
|
data, r_addr = sock.recvfrom(BUF_SIZE)
|
||||||
|
if not data:
|
||||||
|
logging.debug('UDP handle_client: data is empty')
|
||||||
|
return
|
||||||
|
if not self._is_local:
|
||||||
|
addrlen = len(r_addr[0])
|
||||||
|
if addrlen > 255:
|
||||||
|
# drop
|
||||||
|
return
|
||||||
|
data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data
|
||||||
|
response = encrypt.encrypt_all(self._password, self._method, 1,
|
||||||
|
data)
|
||||||
|
if not response:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
data = encrypt.encrypt_all(self._password, self._method, 0,
|
||||||
|
data)
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
header_result = parse_header(data)
|
||||||
|
if header_result is None:
|
||||||
|
return
|
||||||
|
# addrtype, dest_addr, dest_port, header_length = header_result
|
||||||
|
response = '\x00\x00\x00' + data
|
||||||
|
client_addr = self._client_fd_to_server_addr.get(sock.fileno())
|
||||||
|
if client_addr:
|
||||||
|
self._server_socket.sendto(response, client_addr)
|
||||||
|
else:
|
||||||
|
# this packet is from somewhere else we know
|
||||||
|
# simply drop that packet
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_to_loop(self, loop):
|
||||||
|
if self._eventloop:
|
||||||
|
raise Exception('already add to loop')
|
||||||
|
if self._closed:
|
||||||
|
raise Exception('already closed')
|
||||||
|
self._eventloop = loop
|
||||||
|
loop.add_handler(self._handle_events)
|
||||||
|
|
||||||
|
server_socket = self._server_socket
|
||||||
|
self._eventloop.add(server_socket,
|
||||||
|
eventloop.POLL_IN | eventloop.POLL_ERR)
|
||||||
|
|
||||||
|
def remove_to_loop(self):
|
||||||
|
self._eventloop.remove(self._server_socket)
|
||||||
|
self._eventloop.remove_handler(self._handle_events)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
#destroy all conn and server conn
|
||||||
|
self.remove_to_loop()
|
||||||
|
self.close()
|
||||||
|
#GC
|
||||||
|
self._cache = None
|
||||||
|
|
||||||
|
def _handle_events(self, events):
|
||||||
|
for sock, fd, event in events:
|
||||||
|
if sock == self._server_socket:
|
||||||
|
if event & eventloop.POLL_ERR:
|
||||||
|
logging.error('UDP server_socket err')
|
||||||
|
self._handle_server()
|
||||||
|
elif sock and (fd in self._sockets):
|
||||||
|
if event & eventloop.POLL_ERR:
|
||||||
|
logging.error('UDP client_socket err')
|
||||||
|
self._handle_client(sock)
|
||||||
|
now = time.time()
|
||||||
|
if now - self._last_time > 3.5:
|
||||||
|
self._cache.sweep()
|
||||||
|
if now - self._last_time > 7:
|
||||||
|
self._client_fd_to_server_addr.sweep()
|
||||||
|
self._last_time = now
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._closed = True
|
||||||
|
self._server_socket.close()
|
||||||
BIN
Server/shadowsocks/udprelay.pyc
Normal file
BIN
Server/shadowsocks/udprelay.pyc
Normal file
Binary file not shown.
278
Server/shadowsocks/utils.py
Executable file
278
Server/shadowsocks/utils.py
Executable 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
|
||||||
BIN
Server/shadowsocks/utils.pyc
Normal file
BIN
Server/shadowsocks/utils.pyc
Normal file
Binary file not shown.
104
WHMCS/modules/servers/shadowsocks/clientarea.tpl
Normal file
104
WHMCS/modules/servers/shadowsocks/clientarea.tpl
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script src="https://www.gourdata.com/theme/javascript/layer/layer.js"></script>
|
||||||
|
<div style="font-size:12px;text-align:center">
|
||||||
|
<div style="position: relative; overflow: auto; text-align: right; margin:-10px 0 15px 0; font-size: 10px; color: #999;">
|
||||||
|
* 刷新页面可以获取最新的数据,但并非必要的情况下请勿频繁刷新
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
jQuery(document).ready(function($) {
|
||||||
|
$("a[name='qrcode']").on('click',function() {
|
||||||
|
str = $(this).attr('data-qrcode');
|
||||||
|
str = 'ss://' + str;
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
title: $(this).attr('data-title'),
|
||||||
|
shade: [0.8, '#000'],
|
||||||
|
skin: 'layui-layer-demo',
|
||||||
|
closeBtn: 1,
|
||||||
|
shift: 2,
|
||||||
|
shadeClose: true,
|
||||||
|
content: '<img style="width: 100%; height: 100%;" src="https://www.gourdata.com/qr?' + str + '"/><div style="position: relative; overflow: auto; text-align: center; margin-bottom: 10px; font-size: 12px;">请使用 Shadowsocks 客户端进行扫描</div>'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table style="width:100%;border:1px solid #e9e9e9;border-bottom:0;border-collapse:separate;border-spacing:0;border-radius:5px;color:#999;font-size:12px;margin-bottom:5px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center;padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
端口
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
已消耗的上传流量
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
已消耗的下载流量
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center;padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9">
|
||||||
|
{$port}
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9">
|
||||||
|
{$traffic_upload} <span style="color:#BBB;">Kilobyte (KB)</span>
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:10px 15px;border-bottom:1px solid #e9e9e9">
|
||||||
|
{$traffic_download} <span style="color:#BBB;">Kilobyte (KB)</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table style="width:100%;border:1px solid #e9e9e9;border-bottom:0;border-collapse:separate;border-spacing:0;border-radius:5px;color:#999;font-size:12px;margin-bottom:5px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center;padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
每月流量
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
剩余流量
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
最后连接
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center;padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9">
|
||||||
|
{$traffic} <span style="color:#BBB;">Megabyte (MB)</span>
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9">
|
||||||
|
{$traffic_free} <span style="color:#BBB;">Megabyte (MB)</span>
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;padding:10px 15px;border-bottom:1px solid #e9e9e9">
|
||||||
|
{$last_year} <span style="color:#BBB;">年</span> {$last_month} <span style="color:#BBB;">月</span> {$last_day} <span style="color:#BBB;">日</span>, <span style="color:#BBB;">{$times}</span> {$last_time} <span style="color:#BBB;">分</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table style="width:100%;border:1px solid #e9e9e9;border-bottom:0;border-collapse:separate;border-spacing:0;border-radius:5px;color:#999">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
物理地域
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
网协地址
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
别名地址
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
加密方式
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px 10px;background-color:#fcfcfc;border-bottom:1px solid #e9e9e9">
|
||||||
|
扫一扫 <span style="color:red">( New )</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{$node_list}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style="color:#999;border-color:#E9E9E9;padding:10px;border-radius:4px;margin:5px 0;border:1px solid #eee;font-size:12px">
|
||||||
|
注意:由于网协地址可能会发生变化,在无 DNS 污染的环境下建议使用别名地址连接。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
412
WHMCS/modules/servers/shadowsocks/shadowsocks.php
Normal file
412
WHMCS/modules/servers/shadowsocks/shadowsocks.php
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
<?php
|
||||||
|
//判断是否 WHMCS 访问
|
||||||
|
if(!defined("WHMCS")){
|
||||||
|
die("This file cannot be accessed directly");
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_ConfigOptions(){
|
||||||
|
return array(
|
||||||
|
'连接端口' => array(
|
||||||
|
'Type' => 'text',
|
||||||
|
'Description' => 'API 通讯端口'
|
||||||
|
),
|
||||||
|
'流量限制' => array(
|
||||||
|
'Type' => 'text',
|
||||||
|
'Description' => '在 Configoptions 中 traffic 为空时则会启用此处设定'
|
||||||
|
),
|
||||||
|
'计费单位' => array(
|
||||||
|
'Type' => 'dropdown',
|
||||||
|
'Options' => 'MB,GB',
|
||||||
|
'Description' => '可选按照 MB 或 GB 作为流量计费单位'
|
||||||
|
),
|
||||||
|
'节点列表' => array(
|
||||||
|
'Type' => 'textarea',
|
||||||
|
'Description' => '格式为:物理地域 | 网协地址 | 别名地址 | 加密方式'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_CreateAccount($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
if(!empty($params['customfields']['password'])){
|
||||||
|
$adminuser = mysql_fetch_array(mysql_query("SELECT username FROM `tbladmins`"));
|
||||||
|
$adminuser = $adminuser['username'];
|
||||||
|
$values["password2"] = $params["customfields"]['password'];
|
||||||
|
$results = localAPI("encryptpassword",$values,$adminuser);
|
||||||
|
$table = "tblhosting";
|
||||||
|
$update = array("password"=>$results['password']);
|
||||||
|
$where = array("id"=>$params["serviceid"]);
|
||||||
|
update_query($table,$update,$where);
|
||||||
|
$password = $params["customfields"]['password'];
|
||||||
|
}else{
|
||||||
|
$password = $params['password'];
|
||||||
|
}
|
||||||
|
if(!empty($params['configoptions']['traffic'])){
|
||||||
|
$traffic = $params['configoptions']['traffic'];
|
||||||
|
}else{
|
||||||
|
$traffic = $params['configoption2'];
|
||||||
|
}
|
||||||
|
if($params['configoption3'] == "GB"){
|
||||||
|
$traffic = $traffic * 1024;
|
||||||
|
}
|
||||||
|
$command = array(
|
||||||
|
'action' => 'create',
|
||||||
|
'pid' => $pid,
|
||||||
|
'password' => $password,
|
||||||
|
'traffic' => $traffic
|
||||||
|
);
|
||||||
|
$curl_create = curl_init();
|
||||||
|
curl_setopt($curl_create, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_create, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_create, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_create, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_create, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_create, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_create, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_create, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_create, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_create, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$create_data = curl_exec($curl_create);
|
||||||
|
curl_close($curl_create);
|
||||||
|
$create_data = json_decode($create_data, true);
|
||||||
|
if($create_data['status'] != "Error"){
|
||||||
|
insert_query("mod_weektraffic",array(
|
||||||
|
'id' => $pid,
|
||||||
|
'port' => $create_data['result'],
|
||||||
|
'last_time' => time()
|
||||||
|
));
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $create_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_SuspendAccount($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
$command = array(
|
||||||
|
'action' => 'suspend',
|
||||||
|
'pid' => $pid
|
||||||
|
);
|
||||||
|
$curl_suspend = curl_init();
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_suspend, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$suspend_data = curl_exec($curl_suspend);
|
||||||
|
curl_close($curl_suspend);
|
||||||
|
$suspend_data = json_decode($suspend_data, true);
|
||||||
|
if($suspend_data['status'] == "Success"){
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $suspend_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_UnsuspendAccount($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
if(!empty($params['customfields']['password'])){
|
||||||
|
$adminuser = mysql_fetch_array(mysql_query("SELECT username FROM `tbladmins`"));
|
||||||
|
$adminuser = $adminuser['username'];
|
||||||
|
$values["password2"] = $params["customfields"]['password'];
|
||||||
|
$results = localAPI("encryptpassword",$values,$adminuser);
|
||||||
|
$table = "tblhosting";
|
||||||
|
$update = array("password"=>$results['password']);
|
||||||
|
$where = array("id"=>$params["serviceid"]);
|
||||||
|
update_query($table,$update,$where);
|
||||||
|
$password = $params["customfields"]['password'];
|
||||||
|
}else{
|
||||||
|
$password = $params['password'];
|
||||||
|
}
|
||||||
|
$command = array(
|
||||||
|
'action' => 'unsuspend',
|
||||||
|
'pid' => $pid,
|
||||||
|
'password' => $password,
|
||||||
|
);
|
||||||
|
$curl_unspend = curl_init();
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_unspend, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$unspend_data = curl_exec($curl_unspend);
|
||||||
|
curl_close($curl_unspend);
|
||||||
|
$unspend_data = json_decode($unspend_data, true);
|
||||||
|
if($unspend_data['status'] == "Success"){
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $unspend_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_TerminateAccount($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
$command = array(
|
||||||
|
'action' => 'terminate',
|
||||||
|
'pid' => $pid
|
||||||
|
);
|
||||||
|
$curl_terminate = curl_init();
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_terminate, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$terminate_data = curl_exec($curl_terminate);
|
||||||
|
curl_close($curl_terminate);
|
||||||
|
$terminate_data = json_decode($terminate_data, true);
|
||||||
|
if($terminate_data['status'] == "Success"){
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $terminate_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_ChangePackage($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
if(!empty($params['configoptions']['traffic'])){
|
||||||
|
$traffic = $params['configoptions']['traffic'];
|
||||||
|
}else{
|
||||||
|
$traffic = $params['configoption2'];
|
||||||
|
}
|
||||||
|
if($params['configoption3'] == "GB"){
|
||||||
|
$traffic = $traffic * 1024;
|
||||||
|
}
|
||||||
|
$command = array(
|
||||||
|
'action' => 'changepackage',
|
||||||
|
'pid' => $pid,
|
||||||
|
'traffic' => $traffic
|
||||||
|
);
|
||||||
|
$curl_changepackage = curl_init();
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_changepackage, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$changepackage_data = curl_exec($curl_changepackage);
|
||||||
|
curl_close($curl_changepackage);
|
||||||
|
$changepackage_data = json_decode($changepackage_data, true);
|
||||||
|
if($changepackage_data['status'] == "Success"){
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $changepackage_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_ChangePassword($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
$password = $params['password'];
|
||||||
|
$command = array(
|
||||||
|
'action' => 'changepassword',
|
||||||
|
'pid' => $pid,
|
||||||
|
'password' => $password,
|
||||||
|
);
|
||||||
|
$curl_changepassword = curl_init();
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_changepassword, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$changepassword_data = curl_exec($curl_changepassword);
|
||||||
|
curl_close($curl_changepassword);
|
||||||
|
$changepassword_data = json_decode($changepassword_data, true);
|
||||||
|
if($changepassword_data['status'] == "Success"){
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $changepassword_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_reset($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
$command = array(
|
||||||
|
'action' => 'reset',
|
||||||
|
'pid' => $pid
|
||||||
|
);
|
||||||
|
$curl_reset = curl_init();
|
||||||
|
curl_setopt($curl_reset, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_reset, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_reset, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$reset_data = curl_exec($curl_reset);
|
||||||
|
curl_close($curl_reset);
|
||||||
|
$reset_data = json_decode($reset_data, true);
|
||||||
|
if($reset_data['status'] == "Success"){
|
||||||
|
$result = 'success';
|
||||||
|
}else{
|
||||||
|
$result = $reset_data['result'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
// print_r($data);die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_ClientArea($params){
|
||||||
|
$pid = $params['serviceid'];
|
||||||
|
$port = $params['configoption1'];
|
||||||
|
$node = explode("|",$params['configoption4']);
|
||||||
|
$serverip = $params['serverip'];
|
||||||
|
$password = $params['password'];
|
||||||
|
$serveraccesshash = $params['serveraccesshash'];
|
||||||
|
$command = array(
|
||||||
|
'action' => 'query',
|
||||||
|
'pid' => $pid
|
||||||
|
);
|
||||||
|
$curl_query = curl_init();
|
||||||
|
curl_setopt($curl_query, CURLOPT_URL, "http://".$serverip.':'.$port.'/shadowsocksapi.php');
|
||||||
|
curl_setopt($curl_query, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($curl_query, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl_query, CURLOPT_FRESH_CONNECT, 1);
|
||||||
|
curl_setopt($curl_query, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($curl_query, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($curl_query, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($curl_query, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl_query, CURLOPT_POSTFIELDS, $command);
|
||||||
|
curl_setopt($curl_query, CURLOPT_USERAGENT, $serveraccesshash);
|
||||||
|
$query_data = curl_exec($curl_query);
|
||||||
|
curl_close($query_data);
|
||||||
|
$query_data = json_decode($query_data, true);
|
||||||
|
if(!empty($params['configoptions']['traffic'])){
|
||||||
|
$traffic = $params['configoptions']['traffic'];
|
||||||
|
}else{
|
||||||
|
$traffic = $params['configoption2'];
|
||||||
|
}
|
||||||
|
$traffic_upload = $query_data['upload'] / 1024;
|
||||||
|
$traffic_upload = round($traffic_upload, 2);
|
||||||
|
$traffic_download = $query_data['download'] / 1024;
|
||||||
|
$traffic_download = round($traffic_download, 2);
|
||||||
|
$traffic_free = ($traffic * 1048576 - ($traffic_upload + $traffic_download)) / 1024;
|
||||||
|
$traffic_free = round($traffic_free, 2);
|
||||||
|
$traffic = $traffic * 1024;
|
||||||
|
$last_time = date('H', $query_data['last_time']);
|
||||||
|
if($last_time <= "5"){
|
||||||
|
$times = "凌晨";
|
||||||
|
}elseif($last_time <= "9"){
|
||||||
|
$times = "早上";
|
||||||
|
}elseif($last_time <= "14"){
|
||||||
|
$times = "中午";
|
||||||
|
}elseif($last_time <= "18"){
|
||||||
|
$times = "下午";
|
||||||
|
}elseif($last_time <= "22"){
|
||||||
|
$times = "晚上";
|
||||||
|
}else{
|
||||||
|
$times = "深夜";
|
||||||
|
}
|
||||||
|
$last_year = date('Y', $query_data['last_time']);
|
||||||
|
$last_month = date('m', $query_data['last_time']);
|
||||||
|
$last_day = date('d', $query_data['last_time']);
|
||||||
|
$last_time = date('H:i', $query_data['last_time']);
|
||||||
|
|
||||||
|
$node_list = '';
|
||||||
|
$x=0;$count=count($node)-1;
|
||||||
|
while($x <= $count){
|
||||||
|
$name = $node[$x];$x++;
|
||||||
|
$ipaddr = $node[$x];$x++;
|
||||||
|
$cname = $node[$x];$x++;
|
||||||
|
$method = $node[$x];$x++;
|
||||||
|
$ssqrcode = base64_encode($method.':'.$password.':'.$ipaddr.':'.$query_data['port']);
|
||||||
|
$node_list .= "<tr>
|
||||||
|
<td style=\"padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9\">
|
||||||
|
{$name}
|
||||||
|
</td>
|
||||||
|
<td style=\"padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9\">
|
||||||
|
{$ipaddr}
|
||||||
|
</td>
|
||||||
|
<td style=\"padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9\">
|
||||||
|
{$cname}
|
||||||
|
</td>
|
||||||
|
<td style=\"padding:10px 15px;border-right:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9;text-transform:uppercase\">
|
||||||
|
{$method}
|
||||||
|
</td>
|
||||||
|
<td style=\"padding:10px 15px;border-bottom:1px solid #e9e9e9\">
|
||||||
|
<a name=\"qrcode\" data-title=\"{$name}\" data-qrcode=\"{$ssqrcode}\" href=\"javascript:;\" style=\"color:#00afd1;text-decoration:none\">查看二维码图片</a>
|
||||||
|
</td>
|
||||||
|
</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = array(
|
||||||
|
'tabOverviewReplacementTemplate' => 'clientarea',
|
||||||
|
'templateVariables' => array(
|
||||||
|
'traffic' => $traffic,
|
||||||
|
'traffic_upload' => $traffic_upload,
|
||||||
|
'traffic_download' => $traffic_download,
|
||||||
|
'traffic_free' => $traffic_free,
|
||||||
|
'last_year' => $last_year,
|
||||||
|
'last_month' => $last_month,
|
||||||
|
'last_day' => $last_day,
|
||||||
|
'last_time' => $last_time,
|
||||||
|
'times' => $times,
|
||||||
|
'port' => $query_data['port'],
|
||||||
|
'node_list' => $node_list,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocks_AdminCustomButtonArray(){
|
||||||
|
return array(
|
||||||
|
'重置' => 'reset'
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user