Created
June 13, 2012 08:29
-
-
Save fcicq/2922790 to your computer and use it in GitHub Desktop.
A magical proxy ...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# by fcicq, released @ 2011.5.12, MIT License. | |
# eventlet is required. | |
import eventlet | |
from eventlet.green import socket | |
### SETTINGS ### | |
SERVER_PORT = 1080 | |
IPV6_ENABLED = socket.has_ipv6 | |
# IPV6_ENABLED = False | |
# set prefer_ipv4 = true may save large number of dns requests(AAAA) on non-NAT64 dns. | |
# | |
PREFER_IPV4 = False | |
# if PREFER_IPV4 and NAT64: # we may always use NAT64 unless ipv6-only sites. | |
# requires IPV6_ENABLED | |
NAT64 = False | |
LOCAL_POLICY = 0 # 0: forbid, 1: forward | |
NAT64_PREFIX = "64:ff9b::" | |
def str2port(port_str): # from pysocks | |
return (ord(port_str[0]) << 8) + ord(port_str[1]) | |
def port2str(port): | |
return chr((port & 0xff00) >> 8)+ chr(port & 0x00ff) | |
def isprivate(ip_string='0.0.0.0', address_family=socket.AF_INET): | |
ip = socket.inet_pton(address_family, ip_string) | |
if address_family == socket.AF_INET: | |
# print ord(ip[0]), ord(ip[1]), ord(ip[2]), ord(ip[3]) | |
if ip[0] in ['\x10', '\x7f', '\xff']: return True # 10.0.0.0/8, 127.0.0.0/8, 255.0.0.0/8 | |
if ip[0:2] == '\xc0\xa8': return True # 192.168.0.0/16 | |
# 172.16.0.0/12 | |
if ip[0]=='\xac' and ord(ip[1]) >=16 and ord(ip[1])<=31: return True | |
# 169.254.0.0/16, temporary disabled | |
if ip[0:2] == '\xa9\xfe': return True | |
return False | |
elif address_family == socket.AF_INET6: | |
# ::ffff::/96? | |
# fe80::/64? | |
return False # FIXME | |
else: | |
return True | |
def nat64conv(ip): | |
if not IPV6_ENABLED: | |
return None | |
if isprivate(ip): | |
return None | |
ip = socket.inet_pton(socket.AF_INET, ip) | |
prefix = socket.inet_pton(socket.AF_INET6, NAT64_PREFIX) | |
return socket.inet_ntop(socket.AF_INET6, prefix[0:12] + ip) | |
def forward(fd1, fd2): | |
try: | |
while True: | |
d = fd1.recv(8192) | |
if d == '': | |
fd1.shutdown(socket.SHUT_RD) | |
fd2.shutdown(socket.SHUT_WR) | |
fd2.sendall(d) | |
except: | |
pass | |
def resolve(host): | |
# 1st pass | |
if not IPV6_ENABLED or PREFER_IPV4: | |
typ = socket.AF_INET | |
else: | |
typ = socket.AF_INET6 | |
try: | |
dns = socket.getaddrinfo(host, None, typ) | |
for l in dns: | |
if l[0] == typ: | |
return (typ, l[4][0]) | |
except: | |
pass | |
# nothing? 2st pass | |
# swap | |
typ = (typ == socket.AF_INET) and socket.AF_INET6 or socket.AF_INET | |
if not IPV6_ENABLED and typ == socket.AF_INET6: | |
return None | |
try: | |
dns = socket.getaddrinfo(host, None, typ) | |
for l in dns: | |
if l[0] == typ: | |
return (typ, l[4][0]) | |
except: | |
pass | |
return None | |
def recvall(fd, rlen): | |
data = '' | |
received = '' | |
glen = 0 | |
while glen < rlen: | |
try: | |
received = fd.recv(rlen - glen) | |
except socket.timeout: | |
break | |
if received == '': | |
break | |
data = data + received | |
glen += len(received) | |
return data | |
START = 0 | |
SOCKS5_REQ = 1 | |
SOCKS5_FWD = 2 | |
def handle(fd): | |
x = recvall(fd, 1) | |
# print fd.fileno(), fd.getpeername() | |
if x != '\x05': # socks5 only | |
print "Not Socks5" | |
fd.close() | |
return | |
nmethod = recvall(fd, 1) | |
nmethods = [] | |
if nmethod=='' or ord(nmethod) >= 10 or ord(nmethod)==0: | |
print "methods error 1" | |
fd.close() | |
return | |
for i in range(ord(nmethod)): | |
method = recvall(fd, 1) | |
if method == '': | |
fd.close() | |
return | |
nmethods.append(ord(method)) | |
# TODO: add user/passwd support | |
if 0 in nmethods: | |
fd.sendall('\x05\x00') # no auth required # VER MUST BE 05! | |
else: | |
fd.sendall('\x05\xff') # auth, but currently we refuse... | |
fd.close() | |
return | |
#STATUS = SOCKS5_REQ | |
x = recvall(fd, 1) # VER | |
if x == '': | |
print "NO REQ?" | |
fd.close() | |
return | |
#print ord(x) | |
if x != '\x05': | |
print "Bad Request 1" | |
fd.sendall('\x05\x07\x00\x01\x00\x00\x00\x00\x00\x00') # not supported | |
fd.close() | |
return | |
cmd = recvall(fd, 1) # CMD | |
if cmd != '\x01': | |
print "not supported" | |
fd.sendall('\x05\x07\x00\x01\x00\x00\x00\x00\x00\x00') # not supported | |
fd.close() | |
return | |
if recvall(fd, 1) != '\x00': | |
print "Bad Request 2" | |
fd.close() | |
return | |
# TCP | |
atyp = recvall(fd, 1) # ATYP | |
if atyp == '\x01': # IPv4 | |
# fetch 4 bytes | |
# in fact, you dont have to send your bind address. | |
addr = recvall(fd, 4) | |
port = recvall(fd, 2) | |
if not addr or not port: | |
print "Bad request 3" | |
fd.close() | |
return | |
addr = socket.inet_ntop(socket.AF_INET, addr) | |
elif atyp == '\x03': # Domain | |
dlen = recvall(fd, 1) # LEN | |
if dlen == '' or dlen == '\x00': | |
print "Bad Request 3" | |
fd.close() | |
return | |
host = recvall(fd, ord(dlen)) | |
port = recvall(fd, 2) | |
if not host or not port: | |
print "Bad request 3" | |
fd.close() | |
return | |
# fetch addr, fixme | |
tup = resolve(host) | |
if tup == None: | |
fd.sendall('\x05\x04\x00\x01\x00\x00\x00\x00\x00\x00') # address not supported | |
fd.close() | |
return | |
typ, addr = tup | |
print "solv", addr, host, typ | |
if typ == socket.AF_INET6: | |
atyp = '\x04' | |
else: | |
atyp = '\x01' | |
elif atyp == '\x04': # IPv6 | |
addr = recvall(fd, 16) | |
port = recvall(fd, 2) | |
if not addr or not port: | |
print "Bad request 3" | |
fd.close() | |
return | |
addr = socket.inet_ntop(socket.AF_INET6, addr) | |
else: | |
print "not supported" | |
fd.sendall('\x05\x08\x00\x01\x00\x00\x00\x00\x00\x00') # address not supported | |
fd.close() | |
return | |
if atyp == '\x01': # local & NAT64 | |
if isprivate(addr, socket.AF_INET) and not LOCAL_POLICY: | |
fd.sendall('\x05\x02\x00\x01\x00\x00\x00\x00\x00\x00') # local not allowed | |
fd.close() | |
return | |
if NAT64: | |
nat64ip = nat64conv(addr) | |
if nat64ip: | |
print "nat64", addr | |
addr = nat64ip | |
atyp = '\x04' # now IPv6 request | |
else: | |
pass # local & NAT64 -> local | |
port = str2port(port) | |
sock = socket.socket(atyp == '\x04' and socket.AF_INET6 or socket.AF_INET) | |
try: | |
print addr, port | |
sock.connect((addr, port)) # timeout? | |
fd.sendall('\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00') | |
eventlet.spawn_n(forward, fd, sock) | |
eventlet.spawn_n(forward, sock, fd) | |
except: | |
fd.sendall('\x05\x04\x00\x01\x00\x00\x00\x00\x00\x00') # timeout? we may have to... | |
sock.close() | |
fd.close() | |
print "server socket listening on port %d" % SERVER_PORT | |
server = eventlet.listen(('127.0.0.1', SERVER_PORT)) # we only listen on localhost | |
pool = eventlet.GreenPool() | |
while True: | |
try: | |
new_sock, address = server.accept() | |
pool.spawn_n(handle, new_sock) | |
except (SystemExit, KeyboardInterrupt): | |
break |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment