add SMART DIRECT feature
This commit is contained in:
parent
2f0222aa00
commit
16cb75e31e
3 changed files with 109 additions and 10 deletions
|
@ -104,6 +104,10 @@ Please notice that some encryption methods are not available on some environment
|
||||||
Performance
|
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.
|
You may want to install gevent for better performance.
|
||||||
|
|
||||||
$ sudo apt-get install python-gevent
|
$ sudo apt-get install python-gevent
|
||||||
|
|
75
shadowsocks/isdirect.py
Executable file
75
shadowsocks/isdirect.py
Executable 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()
|
|
@ -35,6 +35,12 @@ except ImportError:
|
||||||
gevent = None
|
gevent = None
|
||||||
print >>sys.stderr, 'warning: gevent not found, using threading instead'
|
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 socket
|
||||||
import select
|
import select
|
||||||
import SocketServer
|
import SocketServer
|
||||||
|
@ -62,13 +68,16 @@ class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
||||||
|
|
||||||
|
|
||||||
class Socks5Server(SocketServer.StreamRequestHandler):
|
class Socks5Server(SocketServer.StreamRequestHandler):
|
||||||
def handle_tcp(self, sock, remote):
|
def handle_tcp(self, sock, remote, direct):
|
||||||
try:
|
try:
|
||||||
fdset = [sock, remote]
|
fdset = [sock, remote]
|
||||||
while True:
|
while True:
|
||||||
r, w, e = select.select(fdset, [], [])
|
r, w, e = select.select(fdset, [], [])
|
||||||
if sock in r:
|
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:
|
if len(data) <= 0:
|
||||||
break
|
break
|
||||||
result = send_all(remote, data)
|
result = send_all(remote, data)
|
||||||
|
@ -76,7 +85,10 @@ class Socks5Server(SocketServer.StreamRequestHandler):
|
||||||
raise Exception('failed to send all data')
|
raise Exception('failed to send all data')
|
||||||
|
|
||||||
if remote in r:
|
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:
|
if len(data) <= 0:
|
||||||
break
|
break
|
||||||
result = send_all(sock, data)
|
result = send_all(sock, data)
|
||||||
|
@ -127,24 +139,29 @@ class Socks5Server(SocketServer.StreamRequestHandler):
|
||||||
addr_port = self.rfile.read(2)
|
addr_port = self.rfile.read(2)
|
||||||
addr_to_send += addr_port
|
addr_to_send += addr_port
|
||||||
port = struct.unpack('>H', addr_port)
|
port = struct.unpack('>H', addr_port)
|
||||||
|
direct = SMART_DIRECT and isdirect.isdirect(addr)
|
||||||
try:
|
try:
|
||||||
reply = "\x05\x00\x00\x01"
|
reply = "\x05\x00\x00\x01"
|
||||||
reply += socket.inet_aton('0.0.0.0') + struct.pack(">H", 2222)
|
reply += socket.inet_aton('0.0.0.0') + struct.pack(">H", 2222)
|
||||||
self.wfile.write(reply)
|
self.wfile.write(reply)
|
||||||
# reply immediately
|
# reply immediately
|
||||||
remote = socket.create_connection((SERVER, REMOTE_PORT))
|
if direct:
|
||||||
self.send_encrypt(remote, addr_to_send)
|
remote = socket.create_connection((addr, port[0]))
|
||||||
logging.info('connecting %s:%d' % (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:
|
except socket.error, e:
|
||||||
logging.warn(e)
|
logging.warn(e)
|
||||||
return
|
return
|
||||||
self.handle_tcp(sock, remote)
|
self.handle_tcp(sock, remote, direct)
|
||||||
except socket.error, e:
|
except socket.error, e:
|
||||||
logging.warn(e)
|
logging.warn(e)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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,
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
format='%(asctime)s %(levelname)-8s %(message)s',
|
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||||
|
@ -167,9 +184,10 @@ def main():
|
||||||
METHOD = None
|
METHOD = None
|
||||||
LOCAL = ''
|
LOCAL = ''
|
||||||
IPv6 = False
|
IPv6 = False
|
||||||
|
SMART_DIRECT = False
|
||||||
|
|
||||||
config_path = utils.find_config()
|
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:
|
for key, value in optlist:
|
||||||
if key == '-c':
|
if key == '-c':
|
||||||
config_path = value
|
config_path = value
|
||||||
|
@ -178,8 +196,10 @@ def main():
|
||||||
logging.info('loading config from %s' % config_path)
|
logging.info('loading config from %s' % config_path)
|
||||||
with open(config_path, 'rb') as f:
|
with open(config_path, 'rb') as f:
|
||||||
config = json.load(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:
|
for key, value in optlist:
|
||||||
|
if key == '-d':
|
||||||
|
SMART_DIRECT = (isdirect is not None)
|
||||||
if key == '-p':
|
if key == '-p':
|
||||||
config['server_port'] = int(value)
|
config['server_port'] = int(value)
|
||||||
elif key == '-k':
|
elif key == '-k':
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue