当前位置

翻墙代理的本地部分

我的这个 proxy 代码参考自 SUZUKI Hisao 的 Tiny HTTP Proxy。主要修改的有两点:

  1. 原版的 do_CONNECT 是两个套接字直接互相转发数据,我改成了 SSL 中间人代理.. 而且依赖到 python 2.6 才支持的 server-side ssl wrap
  2. 另外就是自己封装了 descrypto 类,完成和远程 PHP 的加密

配合其运行的代码见
翻墙代理的远程部分
翻墙代理的加密部分

  1. # -*- coding: utf-8 -*-
  2. # 建议 Python 2.6 环境,以支持 https proxy
  3. # Win32 下需安装
  4. # <a href="http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi
  5. #">http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi
  6. #</a> <a href="http://www.voidspace.org.uk/downloads/pycrypto-2.0.1.win32-py2.6.exe
  7. #">http://www.voidspace.org.uk/downloads/pycrypto-2.0.1.win32-py2.6.exe
  8. #</a> 至于 IronPython 目前还没有 server-side ssl 支持,据说 IP 2.6.1 将会有...
  9. try:
  10.     from ipcrypto import descrypto
  11.     #IronPython 里的 socket 不支持 bind 到 '0.0.0.0'
  12.     import platform
  13.     bind_address = (platform.node(), 8000)
  14. except:
  15.     from pycrypto import descrypto
  16.     bind_address = ('0.0.0.0', 8000)
  17.  
  18. import urllib2
  19. import BaseHTTPServer, SocketServer
  20.  
  21. #REMOTEURL/PASSWORD 和国外主机配合
  22. REMOTEURL = '<a href="http://www.dup2.org/blarblarblar.php'
  23. PASSWORD">http://www.dup2.org/blarblarblar.php'
  24. PASSWORD</a> = 'yourpasswordhere'
  25.  
  26. # KEY/CERT 的生成参考 <a href="http://docs.python.org/library/ssl.html
  27. KEYFILE">http://docs.python.org/library/ssl.html
  28. KEYFILE</a> = 'cert.pem'
  29. CERTFILE = 'cert.pem'
  30.  
  31. #自定义允许的IP列表, 给每个IP起个名字帮助记忆
  32. allow_clients = {'127.0.0.1': 'myself'}
  33.  
  34. desobj = descrypto(PASSWORD)
  35.  
  36. skip_headers = ["keep-alive", "proxy-connection", "connection", "accept-encoding"]
  37.  
  38. class pseudofile():
  39.     ''' SSL Pseudo File Object'''
  40.     def __init__(self, sslobj):
  41.         self.sslobj = sslobj
  42.         self.closed = 0
  43.  
  44.     def read(self, size):
  45.         chunks = []
  46.         read = 0
  47.         while read < size:
  48.             data = self.sslobj.read(size-read)
  49.             read += len(data)
  50.             chunks.append(data)
  51.         return ''.join(chunks)
  52.  
  53.     def readline(self):
  54.         line = []
  55.         while 1:
  56.             char = self.sslobj.read(1)
  57.             line.append(char)
  58.             if char == "\n": return ''.join(line)
  59.  
  60.     def write(self, data):
  61.         bytes = len(data)
  62.         while bytes > 0:
  63.             sent = self.sslobj.write(data)
  64.             if sent == bytes:
  65.                 break    # avoid copy
  66.             data = data[sent:]
  67.             bytes = bytes - sent
  68.  
  69.     # 下面两个方法是 BaseHTTPServer 里会调用到的
  70.     def flush(self):
  71.         pass
  72.     close = flush
  73.  
  74. def checkip(f):
  75.     def new_f(_self):
  76.         (ip, port) = _self.client_address
  77.         if ip in allow_clients:
  78.             f(_self)
  79.         else:
  80.             _self.send_error(403)
  81.     return new_f
  82.  
  83. class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  84.  
  85.     @checkip
  86.     def do_GET(self):
  87.         content_length = 0
  88.         if hasattr(self, "sslhost"): self.raw_requestline = "%s https://%s%s %s\r\n" % (self.command, self.sslhost, self.path
  89. , self.request_version)
  90.         h = [self.raw_requestline]
  91.         for kv in self.headers.items():
  92.             if kv[0] == 'content-length':
  93.                 content_length = int(kv[1])
  94.             if kv[0] in skip_headers: continue
  95.             h.append("%s: %s" % kv)
  96.         h.append("connection: close")
  97.         req = "\r\n".join(h) + "\r\n\r\n"
  98.        
  99.         if content_length:
  100.             req += self.rfile.read(content_length)
  101.  
  102.         encreq = desobj.enc(req)
  103.         req = urllib2.Request(REMOTEURL, encreq)
  104.         f = urllib2.urlopen(req)
  105.        
  106.         text_mode = f.read(1)
  107.         response = f.read()
  108.         if text_mode == "1":
  109.             response = desobj.dec(response)
  110.         self.wfile.write(response)
  111.         print 'REQUEST:', self.raw_requestline.strip()
  112.         #有时候一些看起来是 text/* 的请求也是 binary mode,通常是 304 Not Modified
  113.         print 'RESPONSE: %s, %d Bytes' % ('crypted mode' if text_mode == "1" else 'raw mode', len(response))
  114.        
  115.         self.close_connection = 1
  116.  
  117.     @checkip
  118.     def do_CONNECT(self):
  119.         # print self.raw_requestline
  120.         # "CONNECT twitter.com:443 HTTP/1.1"
  121.         self.sslhost = self.raw_requestline.split()[1]
  122.         self.wfile.write(self.protocol_version + " 200 Connection established\r\n")
  123.         self.wfile.write("Proxy-agent: QYB\r\n\r\n")
  124.         # TODO 浏览器端会看到一个警告,但是没有办法;避免警告是不对的,必须让使用者认识到现在是中间人模式
  125.         try:
  126.             import ssl
  127.             self.rfile = pseudofile(ssl.wrap_socket(self.connection, KEYFILE, CERTFILE, True))
  128.             self.wfile = self.rfile
  129.             self.handle_one_request()
  130.         except:
  131.             print 'ssl error:', self.raw_requestline
  132.             self.close_connection = 1
  133.        
  134.     do_PUT = do_GET
  135.     do_POST = do_GET
  136.     do_HEAD = do_GET
  137.     do_DELETE = do_GET
  138.  
  139. class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): pass
  140. httpd = ThreadingHTTPServer(bind_address, ProxyHandler)
  141. httpd.serve_forever()
Topic: