Frist Version

This commit is contained in:
babytomas
2016-05-27 11:18:25 +08:00
parent 9988c60b02
commit 590d54be87
43 changed files with 23215 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
<?php
define('DB_NAME', ''); //数据库名
define('DB_USER', ''); //数据库账户
define('DB_PASS', ''); //数据库密码
define('DB_HOST', 'localhost'); //数据库主机

View 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
View 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
View File

@@ -0,0 +1 @@
0 0 1 * * php -q /home/wwwroot/api.hhh.com/cron.php

Binary file not shown.

View 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
View 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

Binary file not shown.

1
Server/shadowsocks/__init__.py Executable file
View File

@@ -0,0 +1 @@
#!/usr/bin/python

478
Server/shadowsocks/asyncdns.py Executable file
View 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()

Binary file not shown.

93
Server/shadowsocks/asyncmgr.py Executable file
View 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()

Binary file not shown.

133
Server/shadowsocks/common.py Executable file
View 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

Binary file not shown.

10
Server/shadowsocks/config.json Executable file
View 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
View 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()

Binary file not shown.

233
Server/shadowsocks/encrypt.py Executable file
View 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)

Binary file not shown.

View 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)

Binary file not shown.

View 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()

Binary file not shown.

244
Server/shadowsocks/eventloop.py Executable file
View 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))

Binary file not shown.

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
View 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
View 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
View 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)

Binary file not shown.

22
Server/shadowsocks/server.py Executable file
View 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
View 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

Binary file not shown.

657
Server/shadowsocks/tcprelay.py Executable file
View 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()

Binary file not shown.

View 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
View 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()

Binary file not shown.

278
Server/shadowsocks/utils.py Executable file
View 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

Binary file not shown.

View 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>

View 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'
);
}