add SMART DIRECT feature

This commit is contained in:
Zhongke Chen 2013-03-24 11:59:46 +08:00
parent 2f0222aa00
commit 16cb75e31e
3 changed files with 109 additions and 10 deletions

View file

@ -104,6 +104,10 @@ Please notice that some encryption methods are not available on some environment
Performance
------------
You may use -d option to enable SMART DRIRECT feature. If SMART DIRECT is enabled, connection will be established directly to destination IPs in China.
python local.py -d
You may want to install gevent for better performance.
$ sudo apt-get install python-gevent

75
shadowsocks/isdirect.py Executable file
View file

@ -0,0 +1,75 @@
#!/usr/bin/env python
import sys
import socket
import bisect
def ip2int(ip):
return reduce(lambda x,y: x*256+y, [int(x) for x in ip.split('.')])
def init_geolite():
import urllib2
import zipfile
import StringIO
buf = StringIO.StringIO(urllib2.urlopen("http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip").read())
geolite_begin = []
geolite_end = []
for line in zipfile.ZipFile(buf).open('GeoIPCountryWhois.csv').readlines():
ip_begin, ip_end, int_begin, int_end, code = line.strip().split(',')[0:5]
if code == '"CN"':
geolite_begin.append(int(int_begin[1:-1]))
geolite_end.append(int(int_end[1:-1]))
for ip_begin, ip_end in internal_ip:
ipb = ip2int(ip_begin)
ipe = ip2int(ip_end)
i = bisect.bisect(geolite_begin, ipb)
geolite_begin.insert(i, ipb)
geolite_end.insert(i, ipe)
return (geolite_begin, geolite_end)
def ischina(ip_int):
if geolite:
i = bisect.bisect_right(geolite[0], ip_int) -1
if i == 0 or ip_int > geolite[1][i]:
return False
else:
return True
else:
return False
def isdirect(hostname):
hostname = hostname.strip()
try:
ip = socket.gethostbyname(hostname)
except Exception, e:
ip = "0.0.0.0"
ip_int = ip2int(ip)
try:
is_china = ischina(ip_int)
except Exception, e:
is_china = False
return is_china and \
(ip_int not in blacklist_ip) and \
(not any([hostname.endswith(i) for i in blacklist_domain])) \
or \
(any([hostname.endswith(i) for i in whitelist_domain]))
#bad IPs returned by domestic DNS servers
blacklist_ip =[ip2int(ip) for ip in ['60.191.124.236', '180.168.41.175', '93.46.8.89', '203.98.7.65', '8.7.198.45', '78.16.49.15', '46.82.174.68', '243.185.187.39', '243.185.187.30', '159.106.121.75', '37.61.54.158', '159.24.3.173', '0.0.0.0']]
#domains hijacked
blacklist_domain = ['skype.com', 'youtube.com']
#private IP ranges
internal_ip = [("127.0.0.0", "127.255.255.255"), ("192.168.0.0", "192.168.255.255"), ("172.16.0.0", "172.31.255.255"), ("10.0.0.0", "10.255.255.255")]
#private domain names
whitelist_domain = ['local', 'localhost' ]
geolite = init_geolite()
if __name__ == "__main__":
while 1:
line=sys.stdin.readline()
if line == "":
break
print("OK" if isdirect(line) else "ERR")
sys.stdout.flush()

View file

@ -35,6 +35,12 @@ except ImportError:
gevent = None
print >>sys.stderr, 'warning: gevent not found, using threading instead'
try:
import isdirect
except ImportError:
isdirect = None
print >>sys.stderr, 'error importing isdirect, SMART_DIRECT feature disabled'
import socket
import select
import SocketServer
@ -62,13 +68,16 @@ class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
class Socks5Server(SocketServer.StreamRequestHandler):
def handle_tcp(self, sock, remote):
def handle_tcp(self, sock, remote, direct):
try:
fdset = [sock, remote]
while True:
r, w, e = select.select(fdset, [], [])
if sock in r:
data = self.encrypt(sock.recv(4096))
if direct:
data = sock.recv(4096)
else:
data = self.encrypt(sock.recv(4096))
if len(data) <= 0:
break
result = send_all(remote, data)
@ -76,7 +85,10 @@ class Socks5Server(SocketServer.StreamRequestHandler):
raise Exception('failed to send all data')
if remote in r:
data = self.decrypt(remote.recv(4096))
if direct:
data = remote.recv(4096)
else:
data = self.decrypt(remote.recv(4096))
if len(data) <= 0:
break
result = send_all(sock, data)
@ -127,24 +139,29 @@ class Socks5Server(SocketServer.StreamRequestHandler):
addr_port = self.rfile.read(2)
addr_to_send += addr_port
port = struct.unpack('>H', addr_port)
direct = SMART_DIRECT and isdirect.isdirect(addr)
try:
reply = "\x05\x00\x00\x01"
reply += socket.inet_aton('0.0.0.0') + struct.pack(">H", 2222)
self.wfile.write(reply)
# reply immediately
remote = socket.create_connection((SERVER, REMOTE_PORT))
self.send_encrypt(remote, addr_to_send)
logging.info('connecting %s:%d' % (addr, port[0]))
if direct:
remote = socket.create_connection((addr, port[0]))
logging.info('direct connecting %s:%d' % (addr, port[0]))
else:
remote = socket.create_connection((SERVER, REMOTE_PORT))
self.send_encrypt(remote, addr_to_send)
logging.info('connecting %s:%d' % (addr, port[0]))
except socket.error, e:
logging.warn(e)
return
self.handle_tcp(sock, remote)
self.handle_tcp(sock, remote, direct)
except socket.error, e:
logging.warn(e)
def main():
global SERVER, REMOTE_PORT, PORT, KEY, METHOD, LOCAL, IPv6
global SERVER, REMOTE_PORT, PORT, KEY, METHOD, LOCAL, IPv6, SMART_DIRECT
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s %(message)s',
@ -167,9 +184,10 @@ def main():
METHOD = None
LOCAL = ''
IPv6 = False
SMART_DIRECT = False
config_path = utils.find_config()
optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6')
optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6d')
for key, value in optlist:
if key == '-c':
config_path = value
@ -178,8 +196,10 @@ def main():
logging.info('loading config from %s' % config_path)
with open(config_path, 'rb') as f:
config = json.load(f)
optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6')
optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6d')
for key, value in optlist:
if key == '-d':
SMART_DIRECT = (isdirect is not None)
if key == '-p':
config['server_port'] = int(value)
elif key == '-k':