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

220
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
class Socks5Server(SocketServer.StreamRequestHandler):
def handle_tcp(self, sock, remote):
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):
return data.translate(encrypt_table) return data.translate(encrypt_table)
def decrypt(self, data):
def decrypt(data):
return data.translate(decrypt_table) return data.translate(decrypt_table)
def send_encrypt(self, sock, data):
sock.send(self.encrypt(data))
def handle(self): class RemoteHandler(object):
try: def __init__(self, conn, local_handler):
sock = self.connection self.conn = conn
sock.recv(262) self.local_handler = local_handler
sock.send("\x05\x00") conn.on('connect', self.on_connect)
data = self.rfile.read(4) or '\x00' * 4 conn.on('data', self.on_data)
mode = ord(data[1]) conn.on('close', self.on_close)
if mode != 1: conn.on('end', self.on_end)
logging.warn('mode != 1') conn.connect((SERVER, REMOTE_PORT))
def on_connect(self, s):
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 on_data(self, s, data):
data = decrypt(data)
self.local_handler.conn.write(data)
def on_close(self, s):
# self.local_handler.conn.end()
pass
def on_end(self, s):
self.local_handler.conn.end()
class LocalHandler(object):
def on_data(self, s, data):
if self.stage == 5:
data = encrypt(data)
self.remote_handler.conn.write(data)
return return
if self.stage == 0:
self.conn.write('\x05\00')
self.stage = 1
return
if self.stage == 1:
try:
cmd = ord(data[1])
addrtype = ord(data[3]) addrtype = ord(data[3])
addr_to_send = data[3] # TODO check cmd == 1
if addrtype == 1: if addrtype == 1:
addr_ip = self.rfile.read(4) remote_addr = socket.inet_ntoa(data[4:8])
addr = socket.inet_ntoa(addr_ip) remote_port = data[8:10]
addr_to_send += addr_ip self.addr_to_send = data[3:10]
elif addrtype == 3: header_length = 10
addr_len = self.rfile.read(1)
addr = self.rfile.read(ord(addr_len))
addr_to_send += addr_len + addr
elif addrtype == 4: elif addrtype == 4:
addr_ip = self.rfile.read(16) remote_addr = socket.inet_ntop(data[4:20])
addr = socket.inet_ntop(socket.AF_INET6, addr_ip) remote_port = data[20:22]
addr_to_send += addr_ip 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:
logging.warn('addr_type not support') # TODO check addrtype in (1, 3, 4)
# not support raise
return remote_port = struct.unpack('>H', remote_port)[0]
addr_port = self.rfile.read(2) logging.info('connecting %s:%d' % (remote_addr, remote_port))
addr_to_send += addr_port self.conn.write('\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10')
port = struct.unpack('>H', addr_port) remote_conn = ssloop.Socket()
try: self.remote_handler = RemoteHandler(remote_conn, self)
reply = "\x05\x00\x00\x01"
reply += socket.inet_aton('0.0.0.0') + struct.pack(">H", 2222)
self.wfile.write(reply)
# reply immediately
if '-6' in sys.argv[1:]:
remote = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
remote.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
remote.connect((SERVER, REMOTE_PORT, 0, 0))
else:
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
remote.connect((SERVER, REMOTE_PORT))
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)
return
self.handle_tcp(sock, remote)
except socket.error, e:
logging.warn(e)
# TODO save other bytes
self.stage = 4
return
except:
import traceback
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