Skip to content

Instantly share code, notes, and snippets.

@fcicq
Created June 13, 2012 08:29
Show Gist options
  • Save fcicq/2922790 to your computer and use it in GitHub Desktop.
Save fcicq/2922790 to your computer and use it in GitHub Desktop.
A magical proxy ...
#!/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