use ssloop

This commit is contained in:
clowwindy 2013-05-17 16:05:38 +08:00
parent b9a84f9bad
commit ef8b741182
4 changed files with 118 additions and 114 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "ssloop"]
path = ssloop
url = https://github.com/clowwindy/ssloop.git

View file

@ -2,7 +2,7 @@ shadowsocks
=========== ===========
[![Build Status](https://travis-ci.org/clowwindy/shadowsocks.png)](https://travis-ci.org/clowwindy/shadowsocks) [![Build Status](https://travis-ci.org/clowwindy/shadowsocks.png)](https://travis-ci.org/clowwindy/shadowsocks)
Current version: 1.1 Current version: 2.0
shadowsocks is a lightweight tunnel proxy which can help you get through firewalls shadowsocks is a lightweight tunnel proxy which can help you get through firewalls
@ -41,10 +41,6 @@ You can use args to override settings from `config.json`.
python local.py -s server_name -p server_port -l local_port -k password python local.py -s server_name -p server_port -l local_port -k password
python server.py -p server_port -k password python server.py -p server_port -k password
You may want to install gevent for better performance.
$ sudo apt-get install python-gevent
Or: Or:
$ sudo apt-get install libevent-dev python-pip $ sudo apt-get install libevent-dev python-pip

222
local.py
View file

@ -26,23 +26,14 @@ if sys.version_info < (2, 6):
import simplejson as json import simplejson as json
else: else:
import json import json
try:
import gevent, gevent.monkey
gevent.monkey.patch_all(dns=gevent.version_info[0]>=1)
except ImportError:
gevent = None
print >>sys.stderr, 'warning: gevent not found, using threading instead'
import socket
import select
import SocketServer
import struct import struct
import string import string
import hashlib import hashlib
import os import os
import logging import logging
import getopt import getopt
import socket
def get_table(key): def get_table(key):
m = hashlib.md5() m = hashlib.md5()
@ -54,112 +45,126 @@ def get_table(key):
table.sort(lambda x, y: int(a % (ord(x) + i) - a % (ord(y) + i))) table.sort(lambda x, y: int(a % (ord(x) + i) - a % (ord(y) + i)))
return table return table
def send_all(sock, data):
bytes_sent = 0
while True:
r = sock.send(data[bytes_sent:])
if r < 0:
return r
bytes_sent += r
if bytes_sent == len(data):
return bytes_sent
class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): def encrypt(data):
allow_reuse_address = True return data.translate(encrypt_table)
class Socks5Server(SocketServer.StreamRequestHandler): def decrypt(data):
def handle_tcp(self, sock, remote): return data.translate(decrypt_table)
try:
fdset = [sock, remote]
while True:
r, w, e = select.select(fdset, [], [])
if sock in r:
data = sock.recv(4096)
if len(data) <= 0:
break
result = send_all(remote, self.encrypt(data))
if result < len(data):
raise Exception('failed to send all data')
if remote in r:
data = remote.recv(4096)
if len(data) <= 0:
break
result = send_all(sock, self.decrypt(data))
if result < len(data):
raise Exception('failed to send all data')
finally:
sock.close()
remote.close()
def encrypt(self, data): class RemoteHandler(object):
return data.translate(encrypt_table) def __init__(self, conn, local_handler):
self.conn = conn
self.local_handler = local_handler
conn.on('connect', self.on_connect)
conn.on('data', self.on_data)
conn.on('close', self.on_close)
conn.on('end', self.on_end)
conn.connect((SERVER, REMOTE_PORT))
def decrypt(self, data): def on_connect(self, s):
return data.translate(decrypt_table) self.conn.write(encrypt(self.local_handler.addr_to_send))
for piece in self.local_handler.cached_pieces:
self.conn.write(encrypt(piece))
# TODO write cached pieces
self.local_handler.stage = 5
def send_encrypt(self, sock, data): def on_data(self, s, data):
sock.send(self.encrypt(data)) data = decrypt(data)
self.local_handler.conn.write(data)
def handle(self): def on_close(self, s):
try: # self.local_handler.conn.end()
sock = self.connection pass
sock.recv(262)
sock.send("\x05\x00") def on_end(self, s):
data = self.rfile.read(4) or '\x00' * 4 self.local_handler.conn.end()
mode = ord(data[1])
if mode != 1:
logging.warn('mode != 1') class LocalHandler(object):
return def on_data(self, s, data):
addrtype = ord(data[3]) if self.stage == 5:
addr_to_send = data[3] data = encrypt(data)
if addrtype == 1: self.remote_handler.conn.write(data)
addr_ip = self.rfile.read(4) return
addr = socket.inet_ntoa(addr_ip) if self.stage == 0:
addr_to_send += addr_ip self.conn.write('\x05\00')
elif addrtype == 3: self.stage = 1
addr_len = self.rfile.read(1) return
addr = self.rfile.read(ord(addr_len)) if self.stage == 1:
addr_to_send += addr_len + addr
elif addrtype == 4:
addr_ip = self.rfile.read(16)
addr = socket.inet_ntop(socket.AF_INET6, addr_ip)
addr_to_send += addr_ip
else:
logging.warn('addr_type not support')
# not support
return
addr_port = self.rfile.read(2)
addr_to_send += addr_port
port = struct.unpack('>H', addr_port)
try: try:
reply = "\x05\x00\x00\x01" cmd = ord(data[1])
reply += socket.inet_aton('0.0.0.0') + struct.pack(">H", 2222) addrtype = ord(data[3])
self.wfile.write(reply) # TODO check cmd == 1
# reply immediately if addrtype == 1:
if '-6' in sys.argv[1:]: remote_addr = socket.inet_ntoa(data[4:8])
remote = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) remote_port = data[8:10]
remote.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.addr_to_send = data[3:10]
remote.connect((SERVER, REMOTE_PORT, 0, 0)) header_length = 10
elif addrtype == 4:
remote_addr = socket.inet_ntop(data[4:20])
remote_port = data[20:22]
self.addr_to_send = data[3:22]
header_length = 22
elif addrtype == 3:
addr_len = ord(data[4])
remote_addr = data[5:5 + addr_len]
remote_port = data[5 + addr_len:5 + addr_len + 2]
self.addr_to_send = data[3:5 + addr_len + 2]
header_length = 5 + addr_len + 2
else: else:
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TODO check addrtype in (1, 3, 4)
remote.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) raise
remote.connect((SERVER, REMOTE_PORT)) remote_port = struct.unpack('>H', remote_port)[0]
logging.info('connecting %s:%d' % (remote_addr, remote_port))
self.conn.write('\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10')
remote_conn = ssloop.Socket()
self.remote_handler = RemoteHandler(remote_conn, self)
self.send_encrypt(remote, addr_to_send) if len(data) > header_length:
logging.info('connecting %s:%d' % (addr, port[0])) self.cached_pieces.append(data[header_length:])
except socket.error, e:
logging.warn(e) # TODO save other bytes
self.stage = 4
return return
self.handle_tcp(sock, remote) except:
except socket.error, e: import traceback
logging.warn(e) traceback.print_exc()
if self.stage == 4:
self.cached_pieces.append(data)
def on_end(self, s):
if self.remote_handler:
self.remote_handler.conn.end()
def on_close(self, s):
pass
# self.remote_handler.conn.end()
def __init__(self, conn):
self.stage = 0
self.remote = None
self.addr_len = 0
self.addr_to_send = ''
self.conn = conn
self.cached_pieces = []
conn.on('data', self.on_data)
conn.on('end', self.on_end)
conn.on('close', self.on_close)
def on_connection(s, conn):
LocalHandler(conn)
if __name__ == '__main__': if __name__ == '__main__':
os.chdir(os.path.dirname(__file__) or '.') os.chdir(os.path.dirname(__file__) or '.')
print 'shadowsocks v1.1' sys.path.append('./ssloop')
import ssloop
print 'shadowsocks v2.0'
with open('config.json', 'rb') as f: with open('config.json', 'rb') as f:
config = json.load(f) config = json.load(f)
@ -183,18 +188,17 @@ if __name__ == '__main__':
elif key == '-s': elif key == '-s':
SERVER = value SERVER = value
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') datefmt='%Y-%m-%d %H:%M:%S', filemode='a+')
encrypt_table = ''.join(get_table(KEY)) encrypt_table = ''.join(get_table(KEY))
decrypt_table = string.maketrans(encrypt_table, string.maketrans('', '')) decrypt_table = string.maketrans(encrypt_table, string.maketrans('', ''))
try: try:
server = ThreadingTCPServer(('', PORT), Socks5Server)
logging.info("starting server at port %d ..." % PORT) logging.info("starting server at port %d ..." % PORT)
server.serve_forever() loop = ssloop.instance()
except socket.error, e: s = ssloop.Server(('0.0.0.0', PORT))
logging.error(e) s.on('connection', on_connection)
s.listen()
loop.start()
except KeyboardInterrupt: except KeyboardInterrupt:
server.shutdown()
sys.exit(0) sys.exit(0)

1
ssloop Submodule

@ -0,0 +1 @@
Subproject commit bff37894f53ea3d4ea69aef459a37b0336e43c1b