PageRenderTime 44ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/local/proxy.py

https://github.com/wahgo/goagent-1
Python | 3324 lines | 3201 code | 64 blank | 59 comment | 190 complexity | 3f1384f2f53527b2f9d2857c93fffb27 MD5 | raw file
  1. #!/usr/bin/env python
  2. # coding:utf-8
  3. # Based on GAppProxy 2.0.0 by Du XiaoGang <dugang.2008@gmail.com>
  4. # Based on WallProxy 0.4.0 by Hust Moon <www.ehust@gmail.com>
  5. # Contributor:
  6. # Phus Lu <phus.lu@gmail.com>
  7. # Hewig Xu <hewigovens@gmail.com>
  8. # Ayanamist Yang <ayanamist@gmail.com>
  9. # V.E.O <V.E.O@tom.com>
  10. # Max Lv <max.c.lv@gmail.com>
  11. # AlsoTang <alsotang@gmail.com>
  12. # Christopher Meng <i@cicku.me>
  13. # Yonsm Guo <YonsmGuo@gmail.com>
  14. # Parkman <cseparkman@gmail.com>
  15. # Ming Bai <mbbill@gmail.com>
  16. # Bin Yu <yubinlove1991@gmail.com>
  17. # lileixuan <lileixuan@gmail.com>
  18. # Cong Ding <cong@cding.org>
  19. # Zhang Youfu <zhangyoufu@gmail.com>
  20. # Lu Wei <luwei@barfoo>
  21. # Harmony Meow <harmony.meow@gmail.com>
  22. # logostream <logostream@gmail.com>
  23. # Rui Wang <isnowfy@gmail.com>
  24. # Wang Wei Qiang <wwqgtxx@gmail.com>
  25. # Felix Yan <felixonmars@gmail.com>
  26. # Sui Feng <suifeng.me@qq.com>
  27. # QXO <qxodream@gmail.com>
  28. # Geek An <geekan@foxmail.com>
  29. # Poly Rabbit <mcx_221@foxmail.com>
  30. # oxnz <yunxinyi@gmail.com>
  31. # Shusen Liu <liushusen.smart@gmail.com>
  32. # Yad Smood <y.s.inside@gmail.com>
  33. # Chen Shuang <cs0x7f@gmail.com>
  34. # cnfuyu <cnfuyu@gmail.com>
  35. # cuixin <steven.cuixin@gmail.com>
  36. # s2marine0 <s2marine0@gmail.com>
  37. # Toshio Xiang <snachx@gmail.com>
  38. # Bo Tian <dxmtb@163.com>
  39. # Virgil <variousvirgil@gmail.com>
  40. # hub01 <miaojiabumiao@yeah.net>
  41. # v3aqb <sgzz.cj@gmail.com>
  42. # Oling Cat <olingcat@gmail.com>
  43. __version__ = '3.1.18'
  44. import sys
  45. import os
  46. import glob
  47. reload(sys).setdefaultencoding('UTF-8')
  48. sys.dont_write_bytecode = True
  49. sys.path += glob.glob('%s/*.egg' % os.path.dirname(os.path.abspath(__file__)))
  50. try:
  51. import gevent
  52. import gevent.socket
  53. import gevent.server
  54. import gevent.queue
  55. import gevent.monkey
  56. gevent.monkey.patch_all(subprocess=True)
  57. except ImportError:
  58. gevent = None
  59. except TypeError:
  60. gevent.monkey.patch_all()
  61. sys.stderr.write('\033[31m Warning: Please update gevent to the latest 1.0 version!\033[0m\n')
  62. import errno
  63. import time
  64. import struct
  65. import collections
  66. import binascii
  67. import zlib
  68. import itertools
  69. import re
  70. import io
  71. import fnmatch
  72. import traceback
  73. import random
  74. import base64
  75. import string
  76. import hashlib
  77. import uuid
  78. import threading
  79. import thread
  80. import socket
  81. import ssl
  82. import select
  83. import Queue
  84. import SocketServer
  85. import ConfigParser
  86. import BaseHTTPServer
  87. import httplib
  88. import urllib
  89. import urllib2
  90. import urlparse
  91. try:
  92. import dnslib
  93. except ImportError:
  94. dnslib = None
  95. try:
  96. import OpenSSL
  97. except ImportError:
  98. OpenSSL = None
  99. try:
  100. import pygeoip
  101. except ImportError:
  102. pygeoip = None
  103. HAS_PYPY = hasattr(sys, 'pypy_version_info')
  104. NetWorkIOError = (socket.error, ssl.SSLError, OSError) if not OpenSSL else (socket.error, ssl.SSLError, OpenSSL.SSL.Error, OSError)
  105. class Logging(type(sys)):
  106. CRITICAL = 50
  107. FATAL = CRITICAL
  108. ERROR = 40
  109. WARNING = 30
  110. WARN = WARNING
  111. INFO = 20
  112. DEBUG = 10
  113. NOTSET = 0
  114. def __init__(self, *args, **kwargs):
  115. self.level = self.__class__.INFO
  116. self.__set_error_color = lambda: None
  117. self.__set_warning_color = lambda: None
  118. self.__set_debug_color = lambda: None
  119. self.__reset_color = lambda: None
  120. if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
  121. if os.name == 'nt':
  122. import ctypes
  123. SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute
  124. GetStdHandle = ctypes.windll.kernel32.GetStdHandle
  125. self.__set_error_color = lambda: SetConsoleTextAttribute(GetStdHandle(-11), 0x04)
  126. self.__set_warning_color = lambda: SetConsoleTextAttribute(GetStdHandle(-11), 0x06)
  127. self.__set_debug_color = lambda: SetConsoleTextAttribute(GetStdHandle(-11), 0x002)
  128. self.__reset_color = lambda: SetConsoleTextAttribute(GetStdHandle(-11), 0x07)
  129. elif os.name == 'posix':
  130. self.__set_error_color = lambda: sys.stderr.write('\033[31m')
  131. self.__set_warning_color = lambda: sys.stderr.write('\033[33m')
  132. self.__set_debug_color = lambda: sys.stderr.write('\033[32m')
  133. self.__reset_color = lambda: sys.stderr.write('\033[0m')
  134. @classmethod
  135. def getLogger(cls, *args, **kwargs):
  136. return cls(*args, **kwargs)
  137. def basicConfig(self, *args, **kwargs):
  138. self.level = int(kwargs.get('level', self.__class__.INFO))
  139. if self.level > self.__class__.DEBUG:
  140. self.debug = self.dummy
  141. def log(self, level, fmt, *args, **kwargs):
  142. sys.stderr.write('%s - [%s] %s\n' % (level, time.ctime()[4:-5], fmt % args))
  143. def dummy(self, *args, **kwargs):
  144. pass
  145. def debug(self, fmt, *args, **kwargs):
  146. self.__set_debug_color()
  147. self.log('DEBUG', fmt, *args, **kwargs)
  148. self.__reset_color()
  149. def info(self, fmt, *args, **kwargs):
  150. self.log('INFO', fmt, *args)
  151. def warning(self, fmt, *args, **kwargs):
  152. self.__set_warning_color()
  153. self.log('WARNING', fmt, *args, **kwargs)
  154. self.__reset_color()
  155. def warn(self, fmt, *args, **kwargs):
  156. self.warning(fmt, *args, **kwargs)
  157. def error(self, fmt, *args, **kwargs):
  158. self.__set_error_color()
  159. self.log('ERROR', fmt, *args, **kwargs)
  160. self.__reset_color()
  161. def exception(self, fmt, *args, **kwargs):
  162. self.error(fmt, *args, **kwargs)
  163. sys.stderr.write(traceback.format_exc() + '\n')
  164. def critical(self, fmt, *args, **kwargs):
  165. self.__set_error_color()
  166. self.log('CRITICAL', fmt, *args, **kwargs)
  167. self.__reset_color()
  168. logging = sys.modules['logging'] = Logging('logging')
  169. class LRUCache(object):
  170. """http://pypi.python.org/pypi/lru/"""
  171. def __init__(self, max_items=100):
  172. self.cache = {}
  173. self.key_order = []
  174. self.max_items = max_items
  175. def __setitem__(self, key, value):
  176. self.cache[key] = value
  177. self._mark(key)
  178. def __getitem__(self, key):
  179. value = self.cache[key]
  180. self._mark(key)
  181. return value
  182. def __contains__(self, key):
  183. return key in self.cache
  184. def _mark(self, key):
  185. if key in self.key_order:
  186. self.key_order.remove(key)
  187. self.key_order.insert(0, key)
  188. if len(self.key_order) > self.max_items:
  189. index = self.max_items // 2
  190. delitem = self.cache.__delitem__
  191. key_order = self.key_order
  192. any(delitem(key_order[x]) for x in xrange(index, len(key_order)))
  193. self.key_order = self.key_order[:index]
  194. def clear(self):
  195. self.cache = {}
  196. self.key_order = []
  197. class CertUtil(object):
  198. """CertUtil module, based on mitmproxy"""
  199. ca_vendor = 'GoAgent'
  200. ca_keyfile = 'CA.crt'
  201. ca_certdir = 'certs'
  202. ca_lock = threading.Lock()
  203. @staticmethod
  204. def create_ca():
  205. key = OpenSSL.crypto.PKey()
  206. key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
  207. ca = OpenSSL.crypto.X509()
  208. ca.set_serial_number(0)
  209. ca.set_version(2)
  210. subj = ca.get_subject()
  211. subj.countryName = 'CN'
  212. subj.stateOrProvinceName = 'Internet'
  213. subj.localityName = 'Cernet'
  214. subj.organizationName = CertUtil.ca_vendor
  215. subj.organizationalUnitName = '%s Root' % CertUtil.ca_vendor
  216. subj.commonName = '%s CA' % CertUtil.ca_vendor
  217. ca.gmtime_adj_notBefore(0)
  218. ca.gmtime_adj_notAfter(24 * 60 * 60 * 3652)
  219. ca.set_issuer(ca.get_subject())
  220. ca.set_pubkey(key)
  221. ca.add_extensions([
  222. OpenSSL.crypto.X509Extension(b'basicConstraints', True, b'CA:TRUE'),
  223. # OpenSSL.crypto.X509Extension(b'nsCertType', True, b'sslCA'),
  224. OpenSSL.crypto.X509Extension(b'extendedKeyUsage', True, b'serverAuth,clientAuth,emailProtection,timeStamping,msCodeInd,msCodeCom,msCTLSign,msSGC,msEFS,nsSGC'),
  225. OpenSSL.crypto.X509Extension(b'keyUsage', False, b'keyCertSign, cRLSign'),
  226. OpenSSL.crypto.X509Extension(b'subjectKeyIdentifier', False, b'hash', subject=ca), ])
  227. ca.sign(key, 'sha1')
  228. return key, ca
  229. @staticmethod
  230. def dump_ca():
  231. key, ca = CertUtil.create_ca()
  232. with open(CertUtil.ca_keyfile, 'wb') as fp:
  233. fp.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, ca))
  234. fp.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
  235. @staticmethod
  236. def _get_cert(commonname, sans=()):
  237. with open(CertUtil.ca_keyfile, 'rb') as fp:
  238. content = fp.read()
  239. key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, content)
  240. ca = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, content)
  241. pkey = OpenSSL.crypto.PKey()
  242. pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
  243. req = OpenSSL.crypto.X509Req()
  244. subj = req.get_subject()
  245. subj.countryName = 'CN'
  246. subj.stateOrProvinceName = 'Internet'
  247. subj.localityName = 'Cernet'
  248. subj.organizationalUnitName = '%s Branch' % CertUtil.ca_vendor
  249. if commonname[0] == '.':
  250. subj.commonName = '*' + commonname
  251. subj.organizationName = '*' + commonname
  252. sans = ['*'+commonname] + [x for x in sans if x != '*'+commonname]
  253. else:
  254. subj.commonName = commonname
  255. subj.organizationName = commonname
  256. sans = [commonname] + [x for x in sans if x != commonname]
  257. #req.add_extensions([OpenSSL.crypto.X509Extension(b'subjectAltName', True, ', '.join('DNS: %s' % x for x in sans)).encode()])
  258. req.set_pubkey(pkey)
  259. req.sign(pkey, 'sha1')
  260. cert = OpenSSL.crypto.X509()
  261. cert.set_version(2)
  262. try:
  263. cert.set_serial_number(int(hashlib.md5(commonname.encode('utf-8')).hexdigest(), 16))
  264. except OpenSSL.SSL.Error:
  265. cert.set_serial_number(int(time.time()*1000))
  266. cert.gmtime_adj_notBefore(0)
  267. cert.gmtime_adj_notAfter(60 * 60 * 24 * 3652)
  268. cert.set_issuer(ca.get_subject())
  269. cert.set_subject(req.get_subject())
  270. cert.set_pubkey(req.get_pubkey())
  271. if commonname[0] == '.':
  272. sans = ['*'+commonname] + [s for s in sans if s != '*'+commonname]
  273. else:
  274. sans = [commonname] + [s for s in sans if s != commonname]
  275. #cert.add_extensions([OpenSSL.crypto.X509Extension(b'subjectAltName', True, ', '.join('DNS: %s' % x for x in sans))])
  276. cert.sign(key, 'sha1')
  277. certfile = os.path.join(CertUtil.ca_certdir, commonname + '.crt')
  278. with open(certfile, 'wb') as fp:
  279. fp.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
  280. fp.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, pkey))
  281. return certfile
  282. @staticmethod
  283. def get_cert(commonname, sans=()):
  284. if commonname.count('.') >= 2 and [len(x) for x in reversed(commonname.split('.'))] > [2, 4]:
  285. commonname = '.'+commonname.partition('.')[-1]
  286. certfile = os.path.join(CertUtil.ca_certdir, commonname + '.crt')
  287. if os.path.exists(certfile):
  288. return certfile
  289. elif OpenSSL is None:
  290. return CertUtil.ca_keyfile
  291. else:
  292. with CertUtil.ca_lock:
  293. if os.path.exists(certfile):
  294. return certfile
  295. return CertUtil._get_cert(commonname, sans)
  296. @staticmethod
  297. def import_ca(certfile):
  298. commonname = os.path.splitext(os.path.basename(certfile))[0]
  299. sha1digest = 'AB:70:2C:DF:18:EB:E8:B4:38:C5:28:69:CD:4A:5D:EF:48:B4:0E:33'
  300. if OpenSSL:
  301. try:
  302. with open(certfile, 'rb') as fp:
  303. x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, fp.read())
  304. commonname = next(v.decode() for k, v in x509.get_subject().get_components() if k == b'O')
  305. sha1digest = x509.digest('sha1')
  306. except StandardError as e:
  307. logging.error('load_certificate(certfile=%r) failed:%s', certfile, e)
  308. if sys.platform.startswith('win'):
  309. import ctypes
  310. with open(certfile, 'rb') as fp:
  311. certdata = fp.read()
  312. if certdata.startswith(b'-----'):
  313. begin = b'-----BEGIN CERTIFICATE-----'
  314. end = b'-----END CERTIFICATE-----'
  315. certdata = base64.b64decode(b''.join(certdata[certdata.find(begin)+len(begin):certdata.find(end)].strip().splitlines()))
  316. crypt32 = ctypes.WinDLL(b'crypt32.dll'.decode())
  317. store_handle = crypt32.CertOpenStore(10, 0, 0, 0x4000 | 0x20000, b'ROOT'.decode())
  318. if not store_handle:
  319. return -1
  320. X509_ASN_ENCODING = 0x00000001
  321. CERT_FIND_HASH = 0x10000
  322. class CRYPT_HASH_BLOB(ctypes.Structure):
  323. _fields_ = [('cbData', ctypes.c_ulong), ('pbData', ctypes.c_char_p)]
  324. crypt_hash = CRYPT_HASH_BLOB(20, binascii.a2b_hex(sha1digest.replace(':', '')))
  325. crypt_handle = crypt32.CertFindCertificateInStore(store_handle, X509_ASN_ENCODING, 0, CERT_FIND_HASH, ctypes.byref(crypt_hash), None)
  326. if crypt_handle:
  327. crypt32.CertFreeCertificateContext(crypt_handle)
  328. return 0
  329. ret = crypt32.CertAddEncodedCertificateToStore(store_handle, 0x1, certdata, len(certdata), 4, None)
  330. crypt32.CertCloseStore(store_handle, 0)
  331. del crypt32
  332. return 0 if ret else -1
  333. elif sys.platform == 'darwin':
  334. return os.system(('security find-certificate -a -c "%s" | grep "%s" >/dev/null || security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" "%s"' % (commonname, commonname, certfile.decode('utf-8'))).encode('utf-8'))
  335. elif sys.platform.startswith('linux'):
  336. import platform
  337. platform_distname = platform.dist()[0]
  338. if platform_distname == 'Ubuntu':
  339. pemfile = "/etc/ssl/certs/%s.pem" % commonname
  340. new_certfile = "/usr/local/share/ca-certificates/%s.crt" % commonname
  341. if not os.path.exists(pemfile):
  342. return os.system('cp "%s" "%s" && update-ca-certificates' % (certfile, new_certfile))
  343. elif any(os.path.isfile('%s/certutil' % x) for x in os.environ['PATH'].split(os.pathsep)):
  344. return os.system('certutil -L -d sql:$HOME/.pki/nssdb | grep "%s" || certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "%s" -i "%s"' % (commonname, commonname, certfile))
  345. else:
  346. logging.warning('please install *libnss3-tools* package to import GoAgent root ca')
  347. return 0
  348. @staticmethod
  349. def check_ca():
  350. #Check CA exists
  351. capath = os.path.join(os.path.dirname(os.path.abspath(__file__)), CertUtil.ca_keyfile)
  352. certdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), CertUtil.ca_certdir)
  353. if not os.path.exists(capath):
  354. if not OpenSSL:
  355. logging.critical('CA.key is not exist and OpenSSL is disabled, ABORT!')
  356. sys.exit(-1)
  357. if os.path.exists(certdir):
  358. if os.path.isdir(certdir):
  359. any(os.remove(x) for x in glob.glob(certdir+'/*.crt')+glob.glob(certdir+'/.*.crt'))
  360. else:
  361. os.remove(certdir)
  362. os.mkdir(certdir)
  363. CertUtil.dump_ca()
  364. if glob.glob('%s/*.key' % CertUtil.ca_certdir):
  365. for filename in glob.glob('%s/*.key' % CertUtil.ca_certdir):
  366. try:
  367. os.remove(filename)
  368. os.remove(os.path.splitext(filename)[0]+'.crt')
  369. except EnvironmentError:
  370. pass
  371. #Check CA imported
  372. if CertUtil.import_ca(capath) != 0:
  373. logging.warning('install root certificate failed, Please run as administrator/root/sudo')
  374. #Check Certs Dir
  375. if not os.path.exists(certdir):
  376. os.makedirs(certdir)
  377. class DetectMobileBrowser:
  378. """detect mobile function from http://detectmobilebrowsers.com"""
  379. regex_match_a = re.compile(r"(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino", re.I|re.M).search
  380. regex_match_b = re.compile(r"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-", re.I|re.M).search
  381. @staticmethod
  382. def detect(user_agent):
  383. return DetectMobileBrowser.regex_match_a(user_agent) or DetectMobileBrowser.regex_match_b(user_agent)
  384. class SSLConnection(object):
  385. """OpenSSL Connection Wapper"""
  386. def __init__(self, context, sock):
  387. self._context = context
  388. self._sock = sock
  389. self._connection = OpenSSL.SSL.Connection(context, sock)
  390. self._makefile_refs = 0
  391. def __getattr__(self, attr):
  392. if attr not in ('_context', '_sock', '_connection', '_makefile_refs'):
  393. return getattr(self._connection, attr)
  394. def __wait_sock_io(self, io_func, *args, **kwargs):
  395. timeout = self._sock.gettimeout() or 0.1
  396. fd = self._sock.fileno()
  397. while True:
  398. try:
  399. return io_func(*args, **kwargs)
  400. except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantX509LookupError):
  401. sys.exc_clear()
  402. _, _, errors = select.select([fd], [], [fd], timeout)
  403. if errors:
  404. break
  405. except OpenSSL.SSL.WantWriteError:
  406. sys.exc_clear()
  407. _, _, errors = select.select([], [fd], [fd], timeout)
  408. if errors:
  409. break
  410. def accept(self):
  411. sock, addr = self._sock.accept()
  412. client = OpenSSL.SSL.Connection(sock._context, sock)
  413. return client, addr
  414. def do_handshake(self):
  415. return self.__wait_sock_io(self._connection.do_handshake)
  416. def connect(self, *args, **kwargs):
  417. return self.__wait_sock_io(self._connection.connect, *args, **kwargs)
  418. def send(self, data, flags=0):
  419. try:
  420. return self.__wait_sock_io(self._connection.send, data, flags)
  421. except OpenSSL.SSL.SysCallError as e:
  422. if e[0] == -1 and not data:
  423. # errors when writing empty strings are expected and can be ignored
  424. return 0
  425. raise
  426. def recv(self, bufsiz, flags=0):
  427. pending = self._connection.pending()
  428. if pending:
  429. return self._connection.recv(min(pending, bufsiz))
  430. try:
  431. return self.__wait_sock_io(self._connection.recv, bufsiz, flags)
  432. except OpenSSL.SSL.ZeroReturnError:
  433. return ''
  434. def read(self, bufsiz, flags=0):
  435. return self.recv(bufsiz, flags)
  436. def write(self, buf, flags=0):
  437. return self.sendall(buf, flags)
  438. def close(self):
  439. if self._makefile_refs < 1:
  440. self._connection = None
  441. if self._sock:
  442. socket.socket.close(self._sock)
  443. else:
  444. self._makefile_refs -= 1
  445. def makefile(self, mode='r', bufsize=-1):
  446. self._makefile_refs += 1
  447. return socket._fileobject(self, mode, bufsize, close=True)
  448. @staticmethod
  449. def context_builder(ssl_version='SSLv23', ca_certs=None, cipher_suites=('ALL', '!aNULL', '!eNULL')):
  450. protocol_version = getattr(OpenSSL.SSL, '%s_METHOD' % ssl_version)
  451. ssl_context = OpenSSL.SSL.Context(protocol_version)
  452. if ca_certs:
  453. ssl_context.load_verify_locations(os.path.abspath(ca_certs))
  454. ssl_context.set_verify(OpenSSL.SSL.VERIFY_PEER, lambda c, x, e, d, ok: ok)
  455. else:
  456. ssl_context.set_verify(OpenSSL.SSL.VERIFY_NONE, lambda c, x, e, d, ok: ok)
  457. ssl_context.set_cipher_list(':'.join(cipher_suites))
  458. if hasattr(OpenSSL.SSL, 'SESS_CACHE_BOTH'):
  459. ssl_context.set_session_cache_mode(OpenSSL.SSL.SESS_CACHE_BOTH)
  460. return ssl_context
  461. class ProxyUtil(object):
  462. """ProxyUtil module, based on urllib2"""
  463. @staticmethod
  464. def parse_proxy(proxy):
  465. return urllib2._parse_proxy(proxy)
  466. @staticmethod
  467. def get_system_proxy():
  468. proxies = urllib2.getproxies()
  469. return proxies.get('https') or proxies.get('http') or {}
  470. @staticmethod
  471. def get_listen_ip():
  472. listen_ip = '127.0.0.1'
  473. sock = None
  474. try:
  475. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  476. sock.connect(('8.8.8.8', 53))
  477. listen_ip = sock.getsockname()[0]
  478. except socket.error:
  479. pass
  480. finally:
  481. if sock:
  482. sock.close()
  483. return listen_ip
  484. def inflate(data):
  485. return zlib.decompress(data, -zlib.MAX_WBITS)
  486. def deflate(data):
  487. return zlib.compress(data)[2:-4]
  488. def parse_hostport(host, default_port=80):
  489. m = re.match(r'(.+)[#](\d+)$', host)
  490. if m:
  491. return m.group(1).strip('[]'), int(m.group(2))
  492. else:
  493. return host.strip('[]'), default_port
  494. def dnslib_resolve_over_udp(query, dnsservers, timeout, **kwargs):
  495. """
  496. http://gfwrev.blogspot.com/2009/11/gfwdns.html
  497. http://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%93%E5%AD%98%E6%B1%A1%E6%9F%93
  498. http://support.microsoft.com/kb/241352
  499. """
  500. if not isinstance(query, (basestring, dnslib.DNSRecord)):
  501. raise TypeError('query argument requires string/DNSRecord')
  502. blacklist = kwargs.get('blacklist', ())
  503. turstservers = kwargs.get('turstservers', ())
  504. dns_v4_servers = [x for x in dnsservers if ':' not in x]
  505. dns_v6_servers = [x for x in dnsservers if ':' in x]
  506. sock_v4 = sock_v6 = None
  507. socks = []
  508. if dns_v4_servers:
  509. sock_v4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  510. socks.append(sock_v4)
  511. if dns_v6_servers:
  512. sock_v6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
  513. socks.append(sock_v6)
  514. timeout_at = time.time() + timeout
  515. try:
  516. for _ in xrange(4):
  517. try:
  518. for dnsserver in dns_v4_servers:
  519. if isinstance(query, basestring):
  520. query = dnslib.DNSRecord(q=dnslib.DNSQuestion(query))
  521. query_data = query.pack()
  522. sock_v4.sendto(query_data, parse_hostport(dnsserver, 53))
  523. for dnsserver in dns_v6_servers:
  524. if isinstance(query, basestring):
  525. query = dnslib.DNSRecord(q=dnslib.DNSQuestion(query, qtype=dnslib.QTYPE.AAAA))
  526. query_data = query.pack()
  527. sock_v6.sendto(query_data, parse_hostport(dnsserver, 53))
  528. while time.time() < timeout_at:
  529. ins, _, _ = select.select(socks, [], [], 0.1)
  530. for sock in ins:
  531. reply_data, reply_address = sock.recvfrom(512)
  532. reply_server = reply_address[0]
  533. record = dnslib.DNSRecord.parse(reply_data)
  534. iplist = [str(x.rdata) for x in record.rr if x.rtype in (1, 28, 255)]
  535. if any(x in blacklist for x in iplist):
  536. logging.warning('query=%r dnsservers=%r record bad iplist=%r', query, dnsservers, iplist)
  537. elif record.header.rcode and not iplist and reply_server in turstservers:
  538. logging.info('query=%r trust reply_server=%r record rcode=%s', query, reply_server, record.header.rcode)
  539. return record
  540. elif iplist:
  541. logging.debug('query=%r reply_server=%r record iplist=%s', query, reply_server, iplist)
  542. return record
  543. else:
  544. logging.debug('query=%r reply_server=%r record null iplist=%s', query, reply_server, iplist)
  545. continue
  546. except socket.error as e:
  547. logging.warning('handle dns query=%s socket: %r', query, e)
  548. raise socket.gaierror(11004, 'getaddrinfo %r from %r failed' % (query, dnsservers))
  549. finally:
  550. for sock in socks:
  551. sock.close()
  552. def dnslib_resolve_over_tcp(query, dnsservers, timeout, **kwargs):
  553. """dns query over tcp"""
  554. if not isinstance(query, (basestring, dnslib.DNSRecord)):
  555. raise TypeError('query argument requires string/DNSRecord')
  556. blacklist = kwargs.get('blacklist', ())
  557. def do_resolve(query, dnsserver, timeout, queobj):
  558. if isinstance(query, basestring):
  559. qtype = dnslib.QTYPE.AAAA if ':' in dnsserver else dnslib.QTYPE.A
  560. query = dnslib.DNSRecord(q=dnslib.DNSQuestion(query, qtype=qtype))
  561. query_data = query.pack()
  562. sock_family = socket.AF_INET6 if ':' in dnsserver else socket.AF_INET
  563. sock = socket.socket(sock_family)
  564. rfile = None
  565. try:
  566. sock.settimeout(timeout or None)
  567. sock.connect(parse_hostport(dnsserver, 53))
  568. sock.send(struct.pack('>h', len(query_data)) + query_data)
  569. rfile = sock.makefile('r', 1024)
  570. reply_data_length = rfile.read(2)
  571. if len(reply_data_length) < 2:
  572. raise socket.gaierror(11004, 'getaddrinfo %r from %r failed' % (query, dnsserver))
  573. reply_data = rfile.read(struct.unpack('>h', reply_data_length)[0])
  574. record = dnslib.DNSRecord.parse(reply_data)
  575. iplist = [str(x.rdata) for x in record.rr if x.rtype in (1, 28, 255)]
  576. if any(x in blacklist for x in iplist):
  577. logging.debug('query=%r dnsserver=%r record bad iplist=%r', query, dnsserver, iplist)
  578. raise socket.gaierror(11004, 'getaddrinfo %r from %r failed' % (query, dnsserver))
  579. else:
  580. logging.debug('query=%r dnsserver=%r record iplist=%s', query, dnsserver, iplist)
  581. queobj.put(record)
  582. except socket.error as e:
  583. logging.debug('query=%r dnsserver=%r failed %r', query, dnsserver, e)
  584. queobj.put(e)
  585. finally:
  586. if rfile:
  587. rfile.close()
  588. sock.close()
  589. queobj = Queue.Queue()
  590. for dnsserver in dnsservers:
  591. thread.start_new_thread(do_resolve, (query, dnsserver, timeout, queobj))
  592. for i in range(len(dnsservers)):
  593. try:
  594. result = queobj.get(timeout)
  595. except Queue.Empty:
  596. raise socket.gaierror(11004, 'getaddrinfo %r from %r failed' % (query, dnsservers))
  597. if result and not isinstance(result, Exception):
  598. return result
  599. elif i == len(dnsservers) - 1:
  600. logging.warning('dnslib_resolve_over_tcp %r with %s return %r', query, dnsservers, result)
  601. raise socket.gaierror(11004, 'getaddrinfo %r from %r failed' % (query, dnsservers))
  602. def dnslib_record2iplist(record):
  603. """convert dnslib.DNSRecord to iplist"""
  604. assert isinstance(record, dnslib.DNSRecord)
  605. iplist = [x for x in (str(r.rdata) for r in record.rr) if re.match(r'^\d+\.\d+\.\d+\.\d+$', x) or ':' in x]
  606. return iplist
  607. def get_dnsserver_list():
  608. if os.name == 'nt':
  609. import ctypes
  610. import ctypes.wintypes
  611. DNS_CONFIG_DNS_SERVER_LIST = 6
  612. buf = ctypes.create_string_buffer(2048)
  613. ctypes.windll.dnsapi.DnsQueryConfig(DNS_CONFIG_DNS_SERVER_LIST, 0, None, None, ctypes.byref(buf), ctypes.byref(ctypes.wintypes.DWORD(len(buf))))
  614. ipcount = struct.unpack('I', buf[0:4])[0]
  615. iplist = [socket.inet_ntoa(buf[i:i+4]) for i in xrange(4, ipcount*4+4, 4)]
  616. return iplist
  617. elif os.path.isfile('/etc/resolv.conf'):
  618. with open('/etc/resolv.conf', 'rb') as fp:
  619. return re.findall(r'(?m)^nameserver\s+(\S+)', fp.read())
  620. else:
  621. logging.warning("get_dnsserver_list failed: unsupport platform '%s-%s'", sys.platform, os.name)
  622. return []
  623. def spawn_later(seconds, target, *args, **kwargs):
  624. def wrap(*args, **kwargs):
  625. time.sleep(seconds)
  626. return target(*args, **kwargs)
  627. return thread.start_new_thread(wrap, args, kwargs)
  628. def spawn_period(seconds, target, *args, **kwargs):
  629. def wrap(*args, **kwargs):
  630. try:
  631. time.sleep(seconds)
  632. target(*args, **kwargs)
  633. except StandardError as e:
  634. logging.warning('%r(%s, %s) error: %r', target, args, kwargs, e)
  635. return thread.start_new_thread(wrap, args, kwargs)
  636. def is_clienthello(data):
  637. if len(data) < 20:
  638. return False
  639. if data.startswith('\x16\x03'):
  640. # TLSv12/TLSv11/TLSv1/SSLv3
  641. length, = struct.unpack('>h', data[3:5])
  642. return len(data) == 5 + length
  643. elif data[0] == '\x80' and data[2:4] == '\x01\x03':
  644. # SSLv23
  645. return len(data) == 2 + ord(data[1])
  646. else:
  647. return False
  648. def is_google_ip(ipaddr):
  649. return ipaddr.startswith(('173.194.', '207.126.', '209.85.', '216.239.', '64.18.', '64.233.', '66.102.', '66.249.', '72.14.', '74.125.'))
  650. def extract_sni_name(packet):
  651. if packet.startswith('\x16\x03'):
  652. stream = io.BytesIO(packet)
  653. stream.read(0x2b)
  654. session_id_length = ord(stream.read(1))
  655. stream.read(session_id_length)
  656. cipher_suites_length, = struct.unpack('>h', stream.read(2))
  657. stream.read(cipher_suites_length+2)
  658. extensions_length, = struct.unpack('>h', stream.read(2))
  659. # extensions = {}
  660. while True:
  661. data = stream.read(2)
  662. if not data:
  663. break
  664. etype, = struct.unpack('>h', data)
  665. elen, = struct.unpack('>h', stream.read(2))
  666. edata = stream.read(elen)
  667. if etype == 0:
  668. server_name = edata[5:]
  669. return server_name
  670. class URLFetch(object):
  671. """URLFetch for gae/php fetchservers"""
  672. skip_headers = frozenset(['Vary', 'Via', 'X-Forwarded-For', 'Proxy-Authorization', 'Proxy-Connection', 'Upgrade', 'X-Chrome-Variations', 'Connection', 'Cache-Control'])
  673. def __init__(self, fetchserver, create_http_request):
  674. assert isinstance(fetchserver, basestring) and callable(create_http_request)
  675. self.fetchserver = fetchserver
  676. self.create_http_request = create_http_request
  677. def fetch(self, method, url, headers, body, timeout, **kwargs):
  678. if '.appspot.com/' in self.fetchserver:
  679. response = self.__gae_fetch(method, url, headers, body, timeout, **kwargs)
  680. response.app_header_parsed = True
  681. else:
  682. response = self.__php_fetch(method, url, headers, body, timeout, **kwargs)
  683. response.app_header_parsed = False
  684. return response
  685. def __gae_fetch(self, method, url, headers, body, timeout, **kwargs):
  686. rc4crypt = lambda s, k: RC4Cipher(k).encrypt(s) if k else s
  687. if isinstance(body, basestring) and body:
  688. if len(body) < 10 * 1024 * 1024 and 'Content-Encoding' not in headers:
  689. zbody = deflate(body)
  690. if len(zbody) < len(body):
  691. body = zbody
  692. headers['Content-Encoding'] = 'deflate'
  693. headers['Content-Length'] = str(len(body))
  694. # GAE donot allow set `Host` header
  695. if 'Host' in headers:
  696. del headers['Host']
  697. metadata = 'G-Method:%s\nG-Url:%s\n%s' % (method, url, ''.join('G-%s:%s\n' % (k, v) for k, v in kwargs.items() if v))
  698. skip_headers = self.skip_headers
  699. metadata += ''.join('%s:%s\n' % (k.title(), v) for k, v in headers.items() if k not in skip_headers)
  700. # prepare GAE request
  701. request_fetchserver = self.fetchserver
  702. request_method = 'POST'
  703. request_headers = {}
  704. if common.GAE_OBFUSCATE:
  705. request_method = 'GET'
  706. request_fetchserver += '/ps/%s.gif' % uuid.uuid1()
  707. request_headers['X-GOA-PS1'] = base64.b64encode(deflate(metadata)).strip()
  708. if body:
  709. request_headers['X-GOA-PS2'] = base64.b64encode(deflate(body)).strip()
  710. body = ''
  711. if common.GAE_PAGESPEED:
  712. request_fetchserver = re.sub(r'^(\w+://)', r'\g<1>1-ps.googleusercontent.com/h/', request_fetchserver)
  713. else:
  714. metadata = deflate(metadata)
  715. body = '%s%s%s' % (struct.pack('!h', len(metadata)), metadata, body)
  716. if 'rc4' in common.GAE_OPTIONS:
  717. request_headers['X-GOA-Options'] = 'rc4'
  718. body = rc4crypt(body, kwargs.get('password'))
  719. request_headers['Content-Length'] = str(len(body))
  720. # post data
  721. need_crlf = 0 if common.GAE_MODE == 'https' else 1
  722. need_validate = common.GAE_VALIDATE
  723. cache_key = '%s:%d' % (common.HOST_POSTFIX_MAP['.appspot.com'], 443 if common.GAE_MODE == 'https' else 80)
  724. response = self.create_http_request(request_method, request_fetchserver, request_headers, body, timeout, crlf=need_crlf, validate=need_validate, cache_key=cache_key)
  725. response.app_status = response.status
  726. response.app_options = response.getheader('X-GOA-Options', '')
  727. if response.status != 200:
  728. return response
  729. data = response.read(4)
  730. if len(data) < 4:
  731. response.status = 502
  732. response.fp = io.BytesIO(b'connection aborted. too short leadbyte data=' + data)
  733. response.read = response.fp.read
  734. return response
  735. response.status, headers_length = struct.unpack('!hh', data)
  736. data = response.read(headers_length)
  737. if len(data) < headers_length:
  738. response.status = 502
  739. response.fp = io.BytesIO(b'connection aborted. too short headers data=' + data)
  740. response.read = response.fp.read
  741. return response
  742. if 'rc4' not in response.app_options:
  743. response.msg = httplib.HTTPMessage(io.BytesIO(inflate(data)))
  744. else:
  745. response.msg = httplib.HTTPMessage(io.BytesIO(inflate(rc4crypt(data, kwargs.get('password')))))
  746. if kwargs.get('password') and response.fp:
  747. response.fp = CipherFileObject(response.fp, RC4Cipher(kwargs['password']))
  748. return response
  749. def __php_fetch(self, method, url, headers, body, timeout, **kwargs):
  750. if body:
  751. if len(body) < 10 * 1024 * 1024 and 'Content-Encoding' not in headers:
  752. zbody = deflate(body)
  753. if len(zbody) < len(body):
  754. body = zbody
  755. headers['Content-Encoding'] = 'deflate'
  756. headers['Content-Length'] = str(len(body))
  757. skip_headers = self.skip_headers
  758. metadata = 'G-Method:%s\nG-Url:%s\n%s%s' % (method, url, ''.join('G-%s:%s\n' % (k, v) for k, v in kwargs.items() if v), ''.join('%s:%s\n' % (k, v) for k, v in headers.items() if k not in skip_headers))
  759. metadata = deflate(metadata)
  760. app_body = b''.join((struct.pack('!h', len(metadata)), metadata, body))
  761. app_headers = {'Content-Length': len(app_body), 'Content-Type': 'application/octet-stream'}
  762. fetchserver = '%s?%s' % (self.fetchserver, random.random())
  763. crlf = 0
  764. cache_key = '%s//:%s' % urlparse.urlsplit(fetchserver)[:2]
  765. response = self.create_http_request('POST', fetchserver, app_headers, app_body, timeout, crlf=crlf, cache_key=cache_key)
  766. if not response:
  767. raise socket.error(errno.ECONNRESET, 'urlfetch %r return None' % url)
  768. if response.status >= 400:
  769. return response
  770. response.app_status = response.status
  771. need_decrypt = kwargs.get('password') and response.app_status == 200 and response.getheader('Content-Type', '') == 'image/gif' and response.fp
  772. if need_decrypt:
  773. response.fp = CipherFileObject(response.fp, XORCipher(kwargs['password'][0]))
  774. return response
  775. class BaseProxyHandlerFilter(object):
  776. """base proxy handler filter"""
  777. def filter(self, handler):
  778. raise NotImplementedError
  779. class SimpleProxyHandlerFilter(BaseProxyHandlerFilter):
  780. """simple proxy handler filter"""
  781. def filter(self, handler):
  782. if handler.command == 'CONNECT':
  783. return [handler.FORWARD, handler.host, handler.port, handler.connect_timeout]
  784. else:
  785. return [handler.DIRECT, {}]
  786. class AuthFilter(BaseProxyHandlerFilter):
  787. """authorization filter"""
  788. auth_info = "Proxy authentication required"""
  789. white_list = set(['127.0.0.1'])
  790. def __init__(self, username, password):
  791. self.username = username
  792. self.password = password
  793. def check_auth_header(self, auth_header):
  794. method, _, auth_data = auth_header.partition(' ')
  795. if method == 'Basic':
  796. username, _, password = base64.b64decode(auth_data).partition(':')
  797. if username == self.username and password == self.password:
  798. return True
  799. return False
  800. def filter(self, handler):
  801. if self.white_list and handler.client_address[0] in self.white_list:
  802. return None
  803. auth_header = handler.headers.get('Proxy-Authorization') or getattr(handler, 'auth_header', None)
  804. if auth_header and self.check_auth_header(auth_header):
  805. handler.auth_header = auth_header
  806. else:
  807. headers = {'Access-Control-Allow-Origin': '*',
  808. 'Proxy-Authenticate': 'Basic realm="%s"' % self.auth_info,
  809. 'Content-Length': '0',
  810. 'Connection': 'keep-alive'}
  811. return [handler.MOCK, 407, headers, '']
  812. class SimpleProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  813. """SimpleProxyHandler for GoAgent 3.x"""
  814. protocol_version = 'HTTP/1.1'
  815. ssl_version = ssl.PROTOCOL_SSLv23
  816. disable_transport_ssl = True
  817. scheme = 'http'
  818. skip_headers = frozenset(['Vary', 'Via', 'X-Forwarded-For', 'Proxy-Authorization', 'Proxy-Connection', 'Upgrade', 'X-Chrome-Variations', 'Connection', 'Cache-Control'])
  819. bufsize = 256 * 1024
  820. max_timeout = 4
  821. connect_timeout = 2
  822. first_run_lock = threading.Lock()
  823. handler_filters = [SimpleProxyHandlerFilter()]
  824. sticky_filter = None
  825. def finish(self):
  826. """make python2 BaseHTTPRequestHandler happy"""
  827. try:
  828. BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
  829. except NetWorkIOError as e:
  830. if e[0] not in (errno.ECONNABORTED, errno.ECONNRESET, errno.EPIPE):
  831. raise
  832. def address_string(self):
  833. return '%s:%s' % self.client_address[:2]
  834. def send_response(self, code, message=None):
  835. if message is None:
  836. if code in self.responses:
  837. message = self.responses[code][0]
  838. else:
  839. message = ''
  840. if self.request_version != 'HTTP/0.9':
  841. self.wfile.write('%s %d %s\r\n' % (self.protocol_version, code, message))
  842. def send_header(self, keyword, value):
  843. """Send a MIME header."""
  844. base_send_header = BaseHTTPServer.BaseHTTPRequestHandler.send_header
  845. keyword = keyword.title()
  846. if keyword == 'Set-Cookie':
  847. for cookie in re.split(r', (?=[^ =]+(?:=|$))', value):
  848. base_send_header(self, keyword, cookie)
  849. elif keyword == 'Content-Disposition' and '"' not in value:
  850. value = re.sub(r'filename=([^"\']+)', 'filename="\\1"', value)
  851. base_send_header(self, keyword, value)
  852. else:
  853. base_send_header(self, keyword, value)
  854. def setup(self):
  855. if isinstance(self.__class__.first_run, collections.Callable):
  856. try:
  857. with self.__class__.first_run_lock:
  858. if isinstance(self.__class__.first_run, collections.Callable):
  859. self.first_run()
  860. self.__class__.first_run = None
  861. except StandardError as e:
  862. logging.exception('%s.first_run() return %r', self.__class__, e)
  863. self.__class__.setup = BaseHTTPServer.BaseHTTPRequestHandler.setup
  864. self.__class__.do_CONNECT = self.__class__.do_METHOD
  865. self.__class__.do_GET = self.__class__.do_METHOD
  866. self.__class__.do_PUT = self.__class__.do_METHOD
  867. self.__class__.do_POST = self.__class__.do_METHOD
  868. self.__class__.do_HEAD = self.__class__.do_METHOD
  869. self.__class__.do_DELETE = self.__class__.do_METHOD
  870. self.__class__.do_OPTIONS = self.__class__.do_METHOD
  871. self.setup()
  872. def handle_one_request(self):
  873. if not self.disable_transport_ssl and self.scheme == 'http':
  874. leadbyte = self.connection.recv(1, socket.MSG_PEEK)
  875. if leadbyte in ('\x80', '\x16'):
  876. server_name = ''
  877. if leadbyte == '\x16':
  878. for _ in xrange(2):
  879. leaddata = self.connection.recv(1024, socket.MSG_PEEK)
  880. if is_clienthello(leaddata):
  881. try:
  882. server_name = extract_sni_name(leaddata)
  883. finally:
  884. break
  885. try:
  886. certfile = CertUtil.get_cert(server_name or 'www.google.com')
  887. ssl_sock = ssl.wrap_socket(self.connection, ssl_version=self.ssl_version, keyfile=certfile, certfile=certfile, server_side=True)
  888. except StandardError as e:
  889. if e.args[0] not in (errno.ECONNABORTED, errno.ECONNRESET):
  890. logging.exception('ssl.wrap_socket(self.connection=%r) failed: %s', self.connection, e)
  891. return
  892. self.connection = ssl_sock
  893. self.rfile = self.connection.makefile('rb', self.bufsize)
  894. self.wfile = self.connection.makefile('wb', 0)
  895. self.scheme = 'https'
  896. return BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self)
  897. def first_run(self):
  898. pass
  899. def gethostbyname2(self, hostname):
  900. return socket.gethostbyname_ex(hostname)[-1]
  901. def create_tcp_connection(self, hostname, port, timeout, **kwargs):
  902. return socket.create_connection((hostname, port), timeout)
  903. def create_ssl_connection(self, hostname, port, timeout, **kwargs):
  904. sock = self.create_tcp_connection(hostname, port, timeout, **kwargs)
  905. ssl_sock = ssl.wrap_socket(sock, ssl_version=self.ssl_version)
  906. return ssl_sock
  907. def create_http_request(self, method, url, headers, body, timeout, **kwargs):
  908. scheme, netloc, path, query, _ = urlparse.urlsplit(url)
  909. if netloc.rfind(':') <= netloc.rfind(']'):
  910. # no port number
  911. host = netloc
  912. port = 443 if scheme == 'https' else 80
  913. else:
  914. host, _, port = netloc.rpartition(':')
  915. port = int(port)
  916. if query:
  917. path += '?' + query
  918. if 'Host' not in headers:
  919. headers['Host'] = host
  920. if body and 'Content-Length' not in headers:
  921. headers['Content-Length'] = str(len(body))
  922. ConnectionType = httplib.HTTPSConnection if scheme == 'https' else httplib.HTTPConnection
  923. connection = ConnectionType(netloc, timeout=timeout)
  924. connection.request(method, path, body=body, headers=headers)
  925. response = connection.getresponse()
  926. return response
  927. def create_http_request_withserver(self, fetchserver, method, url, headers, body, timeout, **kwargs):
  928. return URLFetch(fetchserver, self.create_http_request).fetch(method, url, headers, body, timeout, **kwargs)
  929. def handle_urlfetch_error(self, fetchserver, response):
  930. pass
  931. def handle_urlfetch_response_close(self, fetchserver, response):
  932. pass
  933. def parse_header(self):
  934. if self.command == 'CONNECT':
  935. netloc = self.path
  936. elif self.path[0] == '/':
  937. netloc = self.headers.get('Host', 'localhost')
  938. self.path = '%s://%s%s' % (self.scheme, netloc, self.path)
  939. else:
  940. netloc = urlparse.urlsplit(self.path).netloc
  941. m = re.match(r'^(.+):(\d+)$', netloc)
  942. if m:
  943. self.host = m.group(1).strip('[]')
  944. self.port = int(m.group(2))
  945. else:
  946. self.host = netloc
  947. self.port = 443 if self.scheme == 'https' else 80
  948. def forward_socket(self, local, remote, timeout):
  949. try:
  950. tick = 1
  951. bufsize = self.bufsize
  952. timecount = timeout
  953. while 1:
  954. timecount -= tick
  955. if timecount <= 0:
  956. break
  957. (ins, _, errors) = select.select([local, remote], [], [local, remote], tick)
  958. if errors:
  959. break
  960. for sock in ins:
  961. data = sock.recv(bufsize)
  962. if not data:
  963. break
  964. if sock is remote:
  965. local.sendall(data)
  966. timecount = timeout
  967. else:
  968. remote.sendall(data)
  969. timecount = timeout
  970. except socket.timeout:
  971. pass
  972. except NetWorkIOError as e:
  973. if e.args[0] not in (errno.ECONNABORTED, errno.ECONNRESET, errno.ENOTCONN, errno.EPIPE):
  974. raise
  975. if e.args[0] in (errno.EBADF,):
  976. return
  977. finally:
  978. for sock in (remote, local):
  979. try:
  980. sock.close()
  981. except StandardError:
  982. pass
  983. def MOCK(self, status, headers, content):
  984. """mock response"""
  985. logging.info('%s "MOCK %s %s %s" %d %d', self.address_string(), self.command, self.path, self.protocol_version, status, len(content))
  986. headers = dict((k.title(), v) for k, v in headers.items())
  987. if 'Transfer-Encoding' in headers:
  988. del headers['Transfer-Encoding']
  989. if 'Content-Length' not in headers:
  990. headers['Content-Length'] = len(content)
  991. if 'Connection' not in headers:
  992. headers['Connection'] = 'close'
  993. self.send_response(status)
  994. for key, value in headers.items():
  995. self.send_header(key, value)
  996. self.end_headers()
  997. self.wfile.write(content)
  998. def STRIP(self, do_ssl_handshake=True, sticky_filter=None):
  999. """strip connect"""
  1000. certfile = CertUtil.get_cert(self.host)
  1001. logging.info('%s "STRIP %s %s:%d %s" - -', self.address_string(), self.command, self.host, self.port, self.protocol_version)
  1002. self.send_response(200)
  1003. self.end_headers()
  1004. if do_ssl_handshake:
  1005. try:
  1006. # ssl_sock = ssl.wrap_socket(self.connection, ssl_version=self.ssl_version, keyfile=certfile, certfile=certfile, server_side=True)
  1007. # bugfix for youtube-dl
  1008. ssl_sock = ssl.wrap_socket(self.connection, keyfile=certfile, certfile=certfile, server_side=True)
  1009. except StandardError as e:
  1010. if e.args[0] not in (errno.ECONNABORTED, errno.ECONNRESET):
  1011. logging.exception('ssl.wrap_socket(self.connection=%r) failed: %s', self.connection, e)
  1012. return
  1013. self.connection = ssl_sock
  1014. self.rfile = self.connection.makefile('rb', self.bufsize)
  1015. self.wfile = self.connection.makefile('wb', 0)
  1016. self.scheme = 'https'
  1017. try:
  1018. self.raw_requestline = self.rfile.readline(65537)
  1019. if len(self.raw_requestline) > 65536:
  1020. self.requestline = ''
  1021. self.request_version = ''
  1022. self.command = ''
  1023. self.send_error(414)
  1024. return
  1025. if not self.raw_requestline:
  1026. self.close_connection = 1
  1027. return
  1028. if not self.parse_request():
  1029. return
  1030. except NetWorkIOError as e:
  1031. if e.args[0] not in (errno.ECONNABORTED, errno.ECONNRESET, errno.EPIPE):
  1032. raise
  1033. self.sticky_filter = sticky_filter
  1034. try:
  1035. self.do_METHOD()
  1036. except NetWorkIOError as e:
  1037. if e.args[0] not in (errno.ECONNABORTED, errno.ETIMEDOUT, errno.EPIPE):
  1038. raise
  1039. def FORWARD(self, hostname, port, timeout, kwargs={}):
  1040. """forward socket"""
  1041. do_ssl_handshake = kwargs.pop('do_ssl_handshake', False)
  1042. local = self.connection
  1043. remote = None
  1044. self.send_response(200)
  1045. self.end_headers()
  1046. self.close_connection = 1
  1047. data = local.recv(1024)
  1048. if not data:
  1049. local.close()
  1050. return
  1051. data_is_clienthello = is_clienthello(data)
  1052. if data_is_clienthello:
  1053. kwargs['client_hello'] = data
  1054. max_retry = kwargs.get('max_retry', 3)
  1055. for i in xrange(max_retry):
  1056. try:
  1057. if do_ssl_handshake:
  1058. remote = self.create_ssl_connection(hostname, port, timeout, **kwargs)
  1059. else:
  1060. remote = self.create_tcp_connection(hostname, port, timeout, **kwargs)
  1061. if not data_is_clienthello and remote and not isinstance(remote, Exception):
  1062. remote.sendall(data)
  1063. break
  1064. except StandardError as e:
  1065. logging.exception('%s "FWD %s %s:%d %s" %r', self.address_string(), self.command, hostname, port, self.protocol_version, e)
  1066. if hasattr(remote, 'close'):
  1067. remote.close()
  1068. if i == max_retry - 1:
  1069. raise
  1070. logging.info('%s "FWD %s %s:%d %s" - -', self.address_string(), self.command, hostname, port, self.protocol_version)
  1071. if hasattr(remote, 'fileno'):
  1072. # reset timeout default to avoid long http upload failure, but it will delay timeout retry :(
  1073. remote.settimeout(None)
  1074. del kwargs
  1075. data = data_is_clienthello and getattr(remote, 'data', None)
  1076. if data:
  1077. del remote.data
  1078. local.sendall(data)
  1079. self.forward_socket(local, remote, self.max_timeout)
  1080. def DIRECT(self, kwargs):
  1081. method = self.command
  1082. if 'url' in kwargs:
  1083. url = kwargs.pop('url')
  1084. elif self.path.lower().startswith(('http://', 'https://', 'ftp://')):
  1085. url = self.path
  1086. else:
  1087. url = 'http://%s%s' % (self.headers['Host'], self.path)
  1088. headers = dict((k.title(), v) for k, v in self.headers.items())
  1089. body = self.body
  1090. response = None
  1091. try:
  1092. response = self.create_http_request(method, url, headers, body, timeout=self.connect_timeout, **kwargs)
  1093. logging.info('%s "DIRECT %s %s %s" %s %s', self.address_string(), self.command, url, self.protocol_version, response.status, response.getheader('Content-Length', '-'))
  1094. response_headers = dict((k.title(), v) for k, v in response.getheaders())
  1095. self.send_response(response.status)
  1096. for key, value in response.getheaders():
  1097. self.send_header(key, value)
  1098. self.end_headers()
  1099. if self.command == 'HEAD' or response.status in (204, 304):
  1100. response.close()
  1101. return
  1102. need_chunked = 'Transfer-Encoding' in response_headers
  1103. while True:
  1104. data = response.read(8192)
  1105. if not data:
  1106. if need_chunked:
  1107. self.wfile.write('0\r\n\r\n')
  1108. break
  1109. if need_chunked:
  1110. self.wfile.write('%x\r\n' % len(data))
  1111. self.wfile.write(data)
  1112. if need_chunked:
  1113. self.wfile.write('\r\n')
  1114. del data
  1115. except (ssl.SSLError, socket.timeout, socket.error):
  1116. if response:
  1117. if response.fp and response.fp._sock:
  1118. response.fp._sock.close()
  1119. response.close()
  1120. finally:
  1121. if response:
  1122. response.close()
  1123. def URLFETCH(self, fetchservers, max_retry=2, kwargs={}):
  1124. """urlfetch from fetchserver"""
  1125. method = self.command
  1126. if self.path[0] == '/':
  1127. url = '%s://%s%s' % (self.scheme, self.headers['Host'], self.path)
  1128. elif self.path.lower().startswith(('http://', 'https://', 'ftp://')):
  1129. url = self.path
  1130. else:
  1131. raise ValueError('URLFETCH %r is not a valid url' % self.path)
  1132. headers = dict((k.title(), v) for k, v in self.headers.items())
  1133. body = self.body
  1134. response = None
  1135. errors = []
  1136. fetchserver = fetchservers[0]
  1137. for i in xrange(max_retry):
  1138. try:
  1139. response = self.create_http_request_withserver(fetchserver, method, url, headers, body, timeout=60, **kwargs)
  1140. if response.app_status < 400:
  1141. break
  1142. else:
  1143. self.handle_urlfetch_error(fetchserver, response)
  1144. if i < max_retry - 1:
  1145. if len(fetchservers) > 1:
  1146. fetchserver = random.choice(fetchservers[1:])
  1147. logging.info('URLFETCH return %d, trying fetchserver=%r', response.app_status, fetchserver)
  1148. response.close()
  1149. except StandardError as e:
  1150. errors.append(e)
  1151. logging.info('URLFETCH "%s %s" fetchserver=%r %r, retry...', method, url, fetchserver, e)
  1152. if len(errors) == max_retry:
  1153. if response and response.app_status >= 500:
  1154. status = response.app_status
  1155. headers = dict(response.getheaders())
  1156. content = response.read()
  1157. response.close()
  1158. else:
  1159. status = 502
  1160. headers = {'Content-Type': 'text/html'}
  1161. content = message_html('502 URLFetch failed', 'Local URLFetch %r failed' % url, '<br>'.join(repr(x) for x in errors))
  1162. return self.MOCK(status, headers, content)
  1163. logging.info('%s "URL %s %s %s" %s %s', self.address_string(), method, url, self.protocol_version, response.status, response.getheader('Content-Length', '-'))
  1164. try:
  1165. if response.status == 206:
  1166. return RangeFetch(self, response, fetchservers, **kwargs).fetch()
  1167. if response.app_header_parsed:
  1168. self.close_connection = not response.getheader('Content-Length')
  1169. self.send_response(response.status)
  1170. for key, value in response.getheaders():
  1171. if key.title() == 'Transfer-Encoding':
  1172. continue
  1173. self.send_header(key, value)
  1174. self.end_headers()
  1175. bufsize = 8192
  1176. while True:
  1177. data = response.read(bufsize)
  1178. if data:
  1179. self.wfile.write(data)
  1180. if not data:
  1181. self.handle_urlfetch_response_close(fetchserver, response)
  1182. response.close()
  1183. break
  1184. del data
  1185. except NetWorkIOError as e:
  1186. if e[0] in (errno.ECONNABORTED, errno.EPIPE) or 'bad write retry' in repr(e):
  1187. return
  1188. def do_METHOD(self):
  1189. self.parse_header()
  1190. self.body = self.rfile.read(int(self.headers['Content-Length'])) if 'Content-Length' in self.headers else ''
  1191. if self.sticky_filter:
  1192. action = self.sticky_filter.filter(self)
  1193. if action:
  1194. return action.pop(0)(*action)
  1195. for handler_filter in self.handler_filters:
  1196. action = handler_filter.filter(self)
  1197. if action:
  1198. return action.pop(0)(*action)
  1199. class RangeFetch(object):
  1200. """Range Fetch Class"""
  1201. threads = 2
  1202. maxsize = 1024*1024*4
  1203. bufsize = 8192
  1204. waitsize = 1024*512
  1205. def __init__(self, handler, response, fetchservers, **kwargs):
  1206. self.handler = handler
  1207. self.url = handler.path
  1208. self.response = response
  1209. self.fetchservers = fetchservers
  1210. self.kwargs = kwargs
  1211. self._stopped = None
  1212. self._last_app_status = {}
  1213. self.expect_begin = 0
  1214. def fetch(self):
  1215. response_status = self.response.status
  1216. response_headers = dict((k.title(), v) for k, v in self.response.getheaders())
  1217. content_range = response_headers['Content-Range']
  1218. #content_length = response_headers['Content-Length']
  1219. start, end, length = tuple(int(x) for x in re.search(r'bytes (\d+)-(\d+)/(\d+)', content_range).group(1, 2, 3))
  1220. if start == 0:
  1221. response_status = 200
  1222. response_headers['Content-Length'] = str(length)
  1223. del response_headers['Content-Range']
  1224. else:
  1225. response_headers['Content-Range'] = 'bytes %s-%s/%s' % (start, end, length)
  1226. response_headers['Content-Length'] = str(length-start)
  1227. logging.info('>>>>>>>>>>>>>>> RangeFetch started(%r) %d-%d', self.url, start, end)
  1228. self.handler.send_response(response_status)
  1229. for key, value in response_headers.items():
  1230. self.handler.send_header(key, value)
  1231. self.handler.end_headers()
  1232. data_queue = Queue.PriorityQueue()
  1233. range_queue = Queue.PriorityQueue()
  1234. range_queue.put((start, end, self.response))
  1235. self.expect_begin = start
  1236. for begin in range(end+1, length, self.maxsize):
  1237. range_queue.put((begin, min(begin+self.maxsize-1, length-1), None))
  1238. for i in xrange(0, self.threads):
  1239. range_delay_size = i * self.maxsize
  1240. spawn_later(float(range_delay_size)/self.waitsize, self.__fetchlet, range_queue, data_queue, range_delay_size)
  1241. has_peek = hasattr(data_queue, 'peek')
  1242. peek_timeout = 120
  1243. while self.expect_begin < length - 1:
  1244. try:
  1245. if has_peek:
  1246. begin, data = data_queue.peek(timeout=peek_timeout)
  1247. if self.expect_begin == begin:
  1248. data_queue.get()
  1249. elif self.expect_begin < begin:
  1250. time.sleep(0.1)
  1251. continue
  1252. else:
  1253. logging.error('RangeFetch Error: begin(%r) < expect_begin(%r), quit.', begin, self.expect_begin)
  1254. break
  1255. else:
  1256. begin, data = data_queue.get(timeout=peek_timeout)
  1257. if self.expect_begin == begin:
  1258. pass
  1259. elif self.expect_begin < begin:
  1260. data_queue.put((begin, data))
  1261. time.sleep(0.1)
  1262. continue
  1263. else:
  1264. logging.error('RangeFetch Error: begin(%r) < expect_begin(%r), quit.', begin, self.expect_begin)
  1265. break
  1266. except Queue.Empty:
  1267. logging.error('data_queue peek timeout, break')
  1268. break
  1269. try:
  1270. self.handler.wfile.write(data)
  1271. self.expect_begin += len(data)
  1272. del data
  1273. except StandardError as e:
  1274. logging.info('RangeFetch client connection aborted(%s).', e)
  1275. break
  1276. self._stopped = True
  1277. def __fetchlet(self, range_queue, data_queue, range_delay_size):
  1278. headers = dict((k.title(), v) for k, v in self.handler.headers.items())
  1279. headers['Connection'] = 'close'
  1280. while 1:
  1281. try:
  1282. if self._stopped:
  1283. return
  1284. try:
  1285. start, end, response = range_queue.get(timeout=1)
  1286. if self.expect_begin < start and data_queue.qsize() * self.bufsize + range_delay_size > 30*1024*1024:
  1287. range_queue.put((start, end, response))
  1288. time.sleep(10)
  1289. continue
  1290. headers['Range'] = 'bytes=%d-%d' % (start, end)
  1291. fetchserver = ''
  1292. if not response:
  1293. fetchserver = random.choice(self.fetchservers)
  1294. if self._last_app_status.get(fetchserver, 200) >= 500:
  1295. time.sleep(5)
  1296. response = self.handler.create_http_request_withserver(fetchserver, self.handler.command, self.url, headers, self.handler.body, timeout=self.handler.connect_timeout, **self.kwargs)
  1297. except Queue.Empty:
  1298. continue
  1299. except StandardError as e:
  1300. logging.warning("Response %r in __fetchlet", e)
  1301. range_queue.put((start, end, None))
  1302. continue
  1303. if not response:
  1304. logging.warning('RangeFetch %s return %r', headers['Range'], response)
  1305. range_queue.put((start, end, None))
  1306. continue
  1307. if fetchserver:
  1308. self._last_app_status[fetchserver] = response.app_status
  1309. if response.app_status != 200:
  1310. logging.warning('Range Fetch "%s %s" %s return %s', self.handler.command, self.url, headers['Range'], response.app_status)
  1311. response.close()
  1312. range_queue.put((start, end, None))
  1313. continue
  1314. if response.getheader('Location'):
  1315. self.url = urlparse.urljoin(self.url, response.getheader('Location'))
  1316. logging.info('RangeFetch Redirect(%r)', self.url)
  1317. response.close()
  1318. range_queue.put((start, end, None))
  1319. continue
  1320. if 200 <= response.status < 300:
  1321. content_range = response.getheader('Content-Range')
  1322. if not content_range:
  1323. logging.warning('RangeFetch "%s %s" return Content-Range=%r: response headers=%r', self.handler.command, self.url, content_range, response.getheaders())
  1324. response.close()
  1325. range_queue.put((start, end, None))
  1326. continue
  1327. content_length = int(response.getheader('Content-Length', 0))
  1328. logging.info('>>>>>>>>>>>>>>> [thread %s] %s %s', threading.currentThread().ident, content_length, content_range)
  1329. while 1:
  1330. try:
  1331. if self._stopped:
  1332. response.close()
  1333. return
  1334. data = response.read(self.bufsize)
  1335. if not data:
  1336. break
  1337. data_queue.put((start, data))
  1338. start += len(data)
  1339. except StandardError as e:
  1340. logging.warning('RangeFetch "%s %s" %s failed: %s', self.handler.command, self.url, headers['Range'], e)
  1341. break
  1342. if start < end + 1:
  1343. logging.warning('RangeFetch "%s %s" retry %s-%s', self.handler.command, self.url, start, end)
  1344. response.close()
  1345. range_queue.put((start, end, None))
  1346. continue
  1347. logging.info('>>>>>>>>>>>>>>> Successfully reached %d bytes.', start - 1)
  1348. else:
  1349. logging.error('RangeFetch %r return %s', self.url, response.status)
  1350. response.close()
  1351. range_queue.put((start, end, None))
  1352. continue
  1353. except StandardError as e:
  1354. logging.exception('RangeFetch._fetchlet error:%s', e)
  1355. raise
  1356. class AdvancedProxyHandler(SimpleProxyHandler):
  1357. """Advanced Proxy Handler"""
  1358. dns_cache = LRUCache(64*1024)
  1359. dns_servers = []
  1360. dns_blacklist = []
  1361. tcp_connection_time = collections.defaultdict(float)
  1362. tcp_connection_time_with_clienthello = collections.defaultdict(float)
  1363. tcp_connection_cache = collections.defaultdict(Queue.PriorityQueue)
  1364. ssl_connection_time = collections.defaultdict(float)
  1365. ssl_connection_cache = collections.defaultdict(Queue.PriorityQueue)
  1366. ssl_connection_good_ipaddrs = {}
  1367. ssl_connection_bad_ipaddrs = {}
  1368. ssl_connection_unknown_ipaddrs = {}
  1369. ssl_connection_keepalive = False
  1370. max_window = 4
  1371. openssl_context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
  1372. def gethostbyname2(self, hostname):
  1373. try:
  1374. iplist = self.dns_cache[hostname]
  1375. except KeyError:
  1376. if re.match(r'^\d+\.\d+\.\d+\.\d+$', hostname) or ':' in hostname:
  1377. iplist = [hostname]
  1378. elif self.dns_servers:
  1379. try:
  1380. record = dnslib_resolve_over_udp(hostname, self.dns_servers, timeout=2, blacklist=self.dns_blacklist)
  1381. except socket.gaierror:
  1382. record = dnslib_resolve_over_tcp(hostname, self.dns_servers, timeout=2, blacklist=self.dns_blacklist)
  1383. iplist = dnslib_record2iplist(record)
  1384. else:
  1385. iplist = socket.gethostbyname_ex(hostname)[-1]
  1386. self.dns_cache[hostname] = iplist
  1387. return iplist
  1388. def create_tcp_connection(self, hostname, port, timeout, **kwargs):
  1389. client_hello = kwargs.get('client_hello', None)
  1390. cache_key = kwargs.get('cache_key') if not client_hello else None
  1391. def create_connection(ipaddr, timeout, queobj):
  1392. sock = None
  1393. try:
  1394. # create a ipv4/ipv6 socket object
  1395. sock = socket.socket(socket.AF_INET if ':' not in ipaddr[0] else socket.AF_INET6)
  1396. # set reuseaddr option to avoid 10048 socket error
  1397. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1398. # resize socket recv buffer 8K->32K to improve browser releated application performance
  1399. sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32*1024)
  1400. # disable nagle algorithm to send http request quickly.
  1401. sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, True)
  1402. # set a short timeout to trigger timeout retry more quickly.
  1403. sock.settimeout(min(self.connect_timeout, timeout))
  1404. # start connection time record
  1405. start_time = time.time()
  1406. # TCP connect
  1407. sock.connect(ipaddr)
  1408. # record TCP connection time
  1409. self.tcp_connection_time[ipaddr] = time.time() - start_time
  1410. # send client hello and peek server hello
  1411. if client_hello:
  1412. sock.sendall(client_hello)
  1413. if gevent and isinstance(sock, gevent.socket.socket):
  1414. sock.data = data = sock.recv(4096)
  1415. else:
  1416. data = sock.recv(4096, socket.MSG_PEEK)
  1417. if not data:
  1418. logging.debug('create_tcp_connection %r with client_hello return NULL byte, continue %r', ipaddr, time.time()-start_time)
  1419. raise socket.timeout('timed out')
  1420. # record TCP connection time with client hello
  1421. self.tcp_connection_time_with_clienthello[ipaddr] = time.time() - start_time
  1422. # set timeout
  1423. sock.settimeout(timeout)
  1424. # put tcp socket object to output queobj
  1425. queobj.put(sock)
  1426. except (socket.error, OSError) as e:
  1427. # any socket.error, put Excpetions to output queobj.
  1428. queobj.put(e)
  1429. # reset a large and random timeout to the ipaddr
  1430. self.tcp_connection_time[ipaddr] = self.connect_timeout+random.random()
  1431. # close tcp socket
  1432. if sock:
  1433. sock.close()
  1434. def close_connection(count, queobj, first_tcp_time):
  1435. for _ in range(count):
  1436. sock = queobj.get()
  1437. tcp_time_threshold = min(1, 1.3 * first_tcp_time)
  1438. if sock and not isinstance(sock, Exception):
  1439. ipaddr = sock.getpeername()
  1440. if cache_key and self.tcp_connection_time[ipaddr] < tcp_time_threshold:
  1441. cache_queue = self.tcp_connection_cache[cache_key]
  1442. if cache_queue.qsize() < 8:
  1443. try:
  1444. _, old_sock = cache_queue.get_nowait()
  1445. old_sock.close()
  1446. except Queue.Empty:
  1447. pass
  1448. cache_queue.put((time.time(), sock))
  1449. else:
  1450. sock.close()
  1451. try:
  1452. while cache_key:
  1453. ctime, sock = self.tcp_connection_cache[cache_key].get_nowait()
  1454. if time.time() - ctime < 30:
  1455. return sock
  1456. else:
  1457. sock.close()
  1458. except Queue.Empty:
  1459. pass
  1460. addresses = [(x, port) for x in self.gethostbyname2(hostname)]
  1461. sock = None
  1462. for _ in range(kwargs.get('max_retry', 3)):
  1463. window = min((self.max_window+1)//2, len(addresses))
  1464. if client_hello:
  1465. addresses.sort(key=self.tcp_connection_time_with_clienthello.__getitem__)
  1466. else:
  1467. addresses.sort(key=self.tcp_connection_time.__getitem__)
  1468. addrs = addresses[:window] + random.sample(addresses, window)
  1469. queobj = gevent.queue.Queue() if gevent else Queue.Queue()
  1470. for addr in addrs:
  1471. thread.start_new_thread(create_connection, (addr, timeout, queobj))
  1472. for i in range(len(addrs)):
  1473. sock = queobj.get()
  1474. if not isinstance(sock, Exception):
  1475. first_tcp_time = self.tcp_connection_time[sock.getpeername()] if not cache_key else 0
  1476. thread.start_new_thread(close_connection, (len(addrs)-i-1, queobj, first_tcp_time))
  1477. return sock
  1478. elif i == 0:
  1479. # only output first error
  1480. logging.warning('create_tcp_connection to %r with %s return %r, try again.', hostname, addrs, sock)
  1481. if isinstance(sock, Exception):
  1482. raise sock
  1483. def create_ssl_connection(self, hostname, port, timeout, **kwargs):
  1484. cache_key = kwargs.get('cache_key')
  1485. validate = kwargs.get('validate')
  1486. def create_connection(ipaddr, timeout, queobj):
  1487. sock = None
  1488. ssl_sock = None
  1489. try:
  1490. # create a ipv4/ipv6 socket object
  1491. sock = socket.socket(socket.AF_INET if ':' not in ipaddr[0] else socket.AF_INET6)
  1492. # set reuseaddr option to avoid 10048 socket error
  1493. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1494. # resize socket recv buffer 8K->32K to improve browser releated application performance
  1495. sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32*1024)
  1496. # disable negal algorithm to send http request quickly.
  1497. sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, True)
  1498. # set a short timeout to trigger timeout retry more quickly.
  1499. sock.settimeout(min(self.connect_timeout, timeout))
  1500. # pick up the certificate
  1501. if not validate:
  1502. ssl_sock = ssl.wrap_socket(sock, ssl_version=self.ssl_version, do_handshake_on_connect=False)
  1503. else:
  1504. ssl_sock = ssl.wrap_socket(sock, ssl_version=self.ssl_version, cert_reqs=ssl.CERT_REQUIRED, ca_certs=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cacert.pem'), do_handshake_on_connect=False)
  1505. ssl_sock.settimeout(min(self.connect_timeout, timeout))
  1506. # start connection time record
  1507. start_time = time.time()
  1508. # TCP connect
  1509. ssl_sock.connect(ipaddr)
  1510. connected_time = time.time()
  1511. # SSL handshake
  1512. ssl_sock.do_handshake()
  1513. handshaked_time = time.time()
  1514. # record TCP connection time
  1515. self.tcp_connection_time[ipaddr] = ssl_sock.tcp_time = connected_time - start_time
  1516. # record SSL connection time
  1517. self.ssl_connection_time[ipaddr] = ssl_sock.ssl_time = handshaked_time - start_time
  1518. ssl_sock.ssl_time = connected_time - start_time
  1519. # sometimes, we want to use raw tcp socket directly(select/epoll), so setattr it to ssl socket.
  1520. ssl_sock.sock = sock
  1521. # remove from bad/unknown ipaddrs dict
  1522. self.ssl_connection_bad_ipaddrs.pop(ipaddr, None)
  1523. self.ssl_connection_unknown_ipaddrs.pop(ipaddr, None)
  1524. # add to good ipaddrs dict
  1525. if ipaddr not in self.ssl_connection_good_ipaddrs:
  1526. self.ssl_connection_good_ipaddrs[ipaddr] = handshaked_time
  1527. # verify SSL certificate.
  1528. if validate and hostname.endswith('.appspot.com'):
  1529. cert = ssl_sock.getpeercert()
  1530. orgname = next((v for ((k, v),) in cert['subject'] if k == 'organizationName'))
  1531. if not orgname.lower().startswith('google '):
  1532. raise ssl.SSLError("%r certificate organizationName(%r) not startswith 'Google'" % (hostname, orgname))
  1533. # set timeout
  1534. ssl_sock.settimeout(timeout)
  1535. # put ssl socket object to output queobj
  1536. queobj.put(ssl_sock)
  1537. except (socket.error, ssl.SSLError, OSError) as e:
  1538. # any socket.error, put Excpetions to output queobj.
  1539. queobj.put(e)
  1540. # reset a large and random timeout to the ipaddr
  1541. self.ssl_connection_time[ipaddr] = self.connect_timeout + random.random()
  1542. # add to bad ipaddrs dict
  1543. if ipaddr not in self.ssl_connection_bad_ipaddrs:
  1544. self.ssl_connection_bad_ipaddrs[ipaddr] = time.time()
  1545. # remove from good/unknown ipaddrs dict
  1546. self.ssl_connection_good_ipaddrs.pop(ipaddr, None)
  1547. self.ssl_connection_unknown_ipaddrs.pop(ipaddr, None)
  1548. # close ssl socket
  1549. if ssl_sock:
  1550. ssl_sock.close()
  1551. # close tcp socket
  1552. if sock:
  1553. sock.close()
  1554. def create_connection_withopenssl(ipaddr, timeout, queobj):
  1555. sock = None
  1556. ssl_sock = None
  1557. try:
  1558. # create a ipv4/ipv6 socket object
  1559. sock = socket.socket(socket.AF_INET if ':' not in ipaddr[0] else socket.AF_INET6)
  1560. # set reuseaddr option to avoid 10048 socket error
  1561. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1562. # resize socket recv buffer 8K->32K to improve browser releated application performance
  1563. sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32*1024)
  1564. # disable negal algorithm to send http request quickly.
  1565. sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, True)
  1566. # set a short timeout to trigger timeout retry more quickly.
  1567. sock.settimeout(timeout or self.connect_timeout)
  1568. # pick up the certificate
  1569. server_hostname = b'www.googleapis.com' if cache_key.startswith('google_') or hostname.endswith('.appspot.com') else None
  1570. ssl_sock = SSLConnection(self.openssl_context, sock)
  1571. ssl_sock.set_connect_state()
  1572. if server_hostname and hasattr(ssl_sock, 'set_tlsext_host_name'):
  1573. ssl_sock.set_tlsext_host_name(server_hostname)
  1574. # start connection time record
  1575. start_time = time.time()
  1576. # TCP connect
  1577. ssl_sock.connect(ipaddr)
  1578. connected_time = time.time()
  1579. # SSL handshake
  1580. ssl_sock.do_handshake()
  1581. handshaked_time = time.time()
  1582. # record TCP connection time
  1583. self.tcp_connection_time[ipaddr] = ssl_sock.tcp_time = connected_time - start_time
  1584. # record SSL connection time
  1585. self.ssl_connection_time[ipaddr] = ssl_sock.ssl_time = handshaked_time - start_time
  1586. # sometimes, we want to use raw tcp socket directly(select/epoll), so setattr it to ssl socket.
  1587. ssl_sock.sock = sock
  1588. # remove from bad/unknown ipaddrs dict
  1589. self.ssl_connection_bad_ipaddrs.pop(ipaddr, None)
  1590. self.ssl_connection_unknown_ipaddrs.pop(ipaddr, None)
  1591. # add to good ipaddrs dict
  1592. if ipaddr not in self.ssl_connection_good_ipaddrs:
  1593. self.ssl_connection_good_ipaddrs[ipaddr] = handshaked_time
  1594. # verify SSL certificate.
  1595. if validate and hostname.endswith('.appspot.com'):
  1596. cert = ssl_sock.get_peer_certificate()
  1597. commonname = next((v for k, v in cert.get_subject().get_components() if k == 'CN'))
  1598. if '.google' not in commonname and not commonname.endswith('.appspot.com'):
  1599. raise socket.error("Host name '%s' doesn't match certificate host '%s'" % (hostname, commonname))
  1600. # put ssl socket object to output queobj
  1601. queobj.put(ssl_sock)
  1602. except (socket.error, OpenSSL.SSL.Error, OSError) as e:
  1603. # any socket.error, put Excpetions to output queobj.
  1604. queobj.put(e)
  1605. # reset a large and random timeout to the ipaddr
  1606. self.ssl_connection_time[ipaddr] = self.connect_timeout + random.random()
  1607. # add to bad ipaddrs dict
  1608. if ipaddr not in self.ssl_connection_bad_ipaddrs:
  1609. self.ssl_connection_bad_ipaddrs[ipaddr] = time.time()
  1610. # remove from good/unknown ipaddrs dict
  1611. self.ssl_connection_good_ipaddrs.pop(ipaddr, None)
  1612. self.ssl_connection_unknown_ipaddrs.pop(ipaddr, None)
  1613. # close ssl socket
  1614. if ssl_sock:
  1615. ssl_sock.close()
  1616. # close tcp socket
  1617. if sock:
  1618. sock.close()
  1619. def close_connection(count, queobj, first_tcp_time, first_ssl_time):
  1620. for _ in range(count):
  1621. sock = queobj.get()
  1622. ssl_time_threshold = min(1, 1.3 * first_ssl_time)
  1623. if sock and not isinstance(sock, Exception):
  1624. if cache_key and sock.ssl_time < ssl_time_threshold:
  1625. cache_queue = self.ssl_connection_cache[cache_key]
  1626. if cache_queue.qsize() < 8:
  1627. try:
  1628. _, old_sock = cache_queue.get_nowait()
  1629. old_sock.close()
  1630. except Queue.Empty:
  1631. pass
  1632. cache_queue.put((time.time(), sock))
  1633. else:
  1634. sock.close()
  1635. def reorg_ipaddrs():
  1636. current_time = time.time()
  1637. for ipaddr, ctime in self.ssl_connection_good_ipaddrs.items():
  1638. if current_time - ctime > 4 * 60:
  1639. self.ssl_connection_good_ipaddrs.pop(ipaddr, None)
  1640. self.ssl_connection_unknown_ipaddrs[ipaddr] = ctime
  1641. for ipaddr, ctime in self.ssl_connection_bad_ipaddrs.items():
  1642. if current_time - ctime > 6 * 60:
  1643. self.ssl_connection_bad_ipaddrs.pop(ipaddr, None)
  1644. self.ssl_connection_unknown_ipaddrs[ipaddr] = ctime
  1645. logging.info("good_ipaddrs=%d, bad_ipaddrs=%d, unkown_ipaddrs=%d", len(self.ssl_connection_good_ipaddrs), len(self.ssl_connection_bad_ipaddrs), len(self.ssl_connection_unknown_ipaddrs))
  1646. try:
  1647. while cache_key:
  1648. ctime, sock = self.ssl_connection_cache[cache_key].get_nowait()
  1649. if time.time() - ctime < 30:
  1650. return sock
  1651. else:
  1652. sock.close()
  1653. except Queue.Empty:
  1654. pass
  1655. addresses = [(x, port) for x in self.gethostbyname2(hostname)]
  1656. sock = None
  1657. for i in range(kwargs.get('max_retry', 5)):
  1658. reorg_ipaddrs()
  1659. window = self.max_window + i
  1660. good_ipaddrs = [x for x in addresses if x in self.ssl_connection_good_ipaddrs]
  1661. good_ipaddrs = sorted(good_ipaddrs, key=self.ssl_connection_time.get)[:window]
  1662. unkown_ipaddrs = [x for x in addresses if x not in self.ssl_connection_good_ipaddrs and x not in self.ssl_connection_bad_ipaddrs]
  1663. random.shuffle(unkown_ipaddrs)
  1664. unkown_ipaddrs = unkown_ipaddrs[:window]
  1665. bad_ipaddrs = [x for x in addresses if x in self.ssl_connection_bad_ipaddrs]
  1666. bad_ipaddrs = sorted(bad_ipaddrs, key=self.ssl_connection_bad_ipaddrs.get)[:window]
  1667. addrs = good_ipaddrs + unkown_ipaddrs + bad_ipaddrs
  1668. remain_window = 3 * window - len(addrs)
  1669. if 0 < remain_window <= len(addresses):
  1670. addrs += random.sample(addresses, remain_window)
  1671. logging.debug('%s good_ipaddrs=%d, unkown_ipaddrs=%r, bad_ipaddrs=%r', cache_key, len(good_ipaddrs), len(unkown_ipaddrs), len(bad_ipaddrs))
  1672. queobj = gevent.queue.Queue() if gevent else Queue.Queue()
  1673. for addr in addrs:
  1674. thread.start_new_thread(create_connection_withopenssl, (addr, timeout, queobj))
  1675. for i in range(len(addrs)):
  1676. sock = queobj.get()
  1677. if not isinstance(sock, Exception):
  1678. thread.start_new_thread(close_connection, (len(addrs)-i-1, queobj, sock.tcp_time, sock.ssl_time))
  1679. return sock
  1680. elif i == 0:
  1681. # only output first error
  1682. logging.warning('create_ssl_connection to %r with %s return %r, try again.', hostname, addrs, sock)
  1683. if isinstance(sock, Exception):
  1684. raise sock
  1685. def create_http_request(self, method, url, headers, body, timeout, max_retry=2, bufsize=8192, crlf=None, validate=None, cache_key=None):
  1686. scheme, netloc, path, query, _ = urlparse.urlsplit(url)
  1687. if netloc.rfind(':') <= netloc.rfind(']'):
  1688. # no port number
  1689. host = netloc
  1690. port = 443 if scheme == 'https' else 80
  1691. else:
  1692. host, _, port = netloc.rpartition(':')
  1693. port = int(port)
  1694. if query:
  1695. path += '?' + query
  1696. if 'Host' not in headers:
  1697. headers['Host'] = host
  1698. if body and 'Content-Length' not in headers:
  1699. headers['Content-Length'] = str(len(body))
  1700. sock = None
  1701. for i in range(max_retry):
  1702. try:
  1703. create_connection = self.create_ssl_connection if scheme == 'https' else self.create_tcp_connection
  1704. sock = create_connection(host, port, timeout, validate=validate, cache_key=cache_key)
  1705. break
  1706. except StandardError as e:
  1707. logging.exception('create_http_request "%s %s" failed:%s', method, url, e)
  1708. if sock:
  1709. sock.close()
  1710. if i == max_retry - 1:
  1711. raise
  1712. request_data = ''
  1713. crlf_counter = 0
  1714. if scheme != 'https' and crlf:
  1715. fakeheaders = dict((k.title(), v) for k, v in headers.items())
  1716. fakeheaders.pop('Content-Length', None)
  1717. fakeheaders.pop('Cookie', None)
  1718. fakeheaders.pop('Host', None)
  1719. if 'User-Agent' not in fakeheaders:
  1720. fakeheaders['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1878.0 Safari/537.36'
  1721. if 'Accept-Language' not in fakeheaders:
  1722. fakeheaders['Accept-Language'] = 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4'
  1723. if 'Accept' not in fakeheaders:
  1724. fakeheaders['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
  1725. fakeheaders_data = ''.join('%s: %s\r\n' % (k, v) for k, v in fakeheaders.items() if k not in self.skip_headers)
  1726. while crlf_counter < 5 or len(request_data) < 1500 * 2:
  1727. request_data += 'GET / HTTP/1.1\r\n%s\r\n' % fakeheaders_data
  1728. crlf_counter += 1
  1729. request_data += '\r\n\r\n\r\n'
  1730. request_data += '%s %s %s\r\n' % (method, path, self.protocol_version)
  1731. request_data += ''.join('%s: %s\r\n' % (k.title(), v) for k, v in headers.items() if k.title() not in self.skip_headers)
  1732. request_data += '\r\n'
  1733. if isinstance(body, bytes):
  1734. sock.sendall(request_data.encode() + body)
  1735. elif hasattr(body, 'read'):
  1736. sock.sendall(request_data)
  1737. while 1:
  1738. data = body.read(bufsize)
  1739. if not data:
  1740. break
  1741. sock.sendall(data)
  1742. else:
  1743. raise TypeError('create_http_request(body) must be a string or buffer, not %r' % type(body))
  1744. response = None
  1745. try:
  1746. while crlf_counter:
  1747. if sys.version[:3] == '2.7':
  1748. response = httplib.HTTPResponse(sock, buffering=False)
  1749. else:
  1750. response = httplib.HTTPResponse(sock)
  1751. response.fp.close()
  1752. response.fp = sock.makefile('rb', 0)
  1753. response.begin()
  1754. response.read()
  1755. response.close()
  1756. crlf_counter -= 1
  1757. except StandardError as e:
  1758. logging.exception('crlf skip read host=%r path=%r error: %r', headers.get('Host'), path, e)
  1759. if response:
  1760. if response.fp and response.fp._sock:
  1761. response.fp._sock.close()
  1762. response.close()
  1763. if sock:
  1764. sock.close()
  1765. return None
  1766. if sys.version[:3] == '2.7':
  1767. response = httplib.HTTPResponse(sock, buffering=True)
  1768. else:
  1769. response = httplib.HTTPResponse(sock)
  1770. response.fp.close()
  1771. response.fp = sock.makefile('rb')
  1772. response.begin()
  1773. if self.ssl_connection_keepalive and scheme == 'https' and cache_key:
  1774. response.cache_key = cache_key
  1775. response.cache_sock = response.fp._sock
  1776. return response
  1777. def handle_urlfetch_response_close(self, fetchserver, response):
  1778. cache_sock = getattr(response, 'cache_sock', None)
  1779. if cache_sock:
  1780. if self.scheme == 'https':
  1781. self.ssl_connection_cache[response.cache_key].put((time.time(), cache_sock))
  1782. else:
  1783. cache_sock.close()
  1784. del response.cache_sock
  1785. def handle_urlfetch_error(self, fetchserver, response):
  1786. pass
  1787. class Common(object):
  1788. """Global Config Object"""
  1789. ENV_CONFIG_PREFIX = 'GOAGENT_'
  1790. def __init__(self):
  1791. """load config from proxy.ini"""
  1792. ConfigParser.RawConfigParser.OPTCRE = re.compile(r'(?P<option>[^=\s][^=]*)\s*(?P<vi>[=])\s*(?P<value>.*)$')
  1793. self.CONFIG = ConfigParser.ConfigParser()
  1794. self.CONFIG_FILENAME = os.path.splitext(os.path.abspath(__file__))[0]+'.ini'
  1795. self.CONFIG_USER_FILENAME = re.sub(r'\.ini$', '.user.ini', self.CONFIG_FILENAME)
  1796. self.CONFIG.read([self.CONFIG_FILENAME, self.CONFIG_USER_FILENAME])
  1797. for key, value in os.environ.items():
  1798. m = re.match(r'^%s([A-Z]+)_([A-Z\_\-]+)$' % self.ENV_CONFIG_PREFIX, key)
  1799. if m:
  1800. self.CONFIG.set(m.group(1).lower(), m.group(2).lower(), value)
  1801. self.LISTEN_IP = self.CONFIG.get('listen', 'ip')
  1802. self.LISTEN_PORT = self.CONFIG.getint('listen', 'port')
  1803. self.LISTEN_USERNAME = self.CONFIG.get('listen', 'username') if self.CONFIG.has_option('listen', 'username') else ''
  1804. self.LISTEN_PASSWORD = self.CONFIG.get('listen', 'password') if self.CONFIG.has_option('listen', 'password') else ''
  1805. self.LISTEN_VISIBLE = self.CONFIG.getint('listen', 'visible')
  1806. self.LISTEN_DEBUGINFO = self.CONFIG.getint('listen', 'debuginfo')
  1807. self.GAE_APPIDS = re.findall(r'[\w\-\.]+', self.CONFIG.get('gae', 'appid').replace('.appspot.com', ''))
  1808. self.GAE_PASSWORD = self.CONFIG.get('gae', 'password').strip()
  1809. self.GAE_PATH = self.CONFIG.get('gae', 'path')
  1810. self.GAE_MODE = self.CONFIG.get('gae', 'mode')
  1811. self.GAE_PROFILE = self.CONFIG.get('gae', 'profile').strip()
  1812. self.GAE_WINDOW = self.CONFIG.getint('gae', 'window')
  1813. self.GAE_KEEPALIVE = self.CONFIG.getint('gae', 'keepalive') if self.CONFIG.has_option('gae', 'keepalive') else 0
  1814. self.GAE_OBFUSCATE = self.CONFIG.getint('gae', 'obfuscate')
  1815. self.GAE_VALIDATE = self.CONFIG.getint('gae', 'validate')
  1816. self.GAE_TRANSPORT = self.CONFIG.getint('gae', 'transport') if self.CONFIG.has_option('gae', 'transport') else 0
  1817. self.GAE_OPTIONS = self.CONFIG.get('gae', 'options')
  1818. self.GAE_REGIONS = set(x.upper() for x in self.CONFIG.get('gae', 'regions').split('|') if x.strip())
  1819. self.GAE_SSLVERSION = self.CONFIG.get('gae', 'sslversion')
  1820. self.GAE_PAGESPEED = self.CONFIG.getint('gae', 'pagespeed') if self.CONFIG.has_option('gae', 'pagespeed') else 0
  1821. if self.GAE_PROFILE == 'auto':
  1822. try:
  1823. socket.create_connection(('2001:4860:4860::8888', 53), timeout=1).close()
  1824. logging.info('Use profile ipv6')
  1825. self.GAE_PROFILE = 'ipv6'
  1826. except socket.error as e:
  1827. logging.info('Fail try profile ipv6 %r, fallback ipv4', e)
  1828. self.GAE_PROFILE = 'ipv4'
  1829. hosts_section, http_section = '%s/hosts' % self.GAE_PROFILE, '%s/http' % self.GAE_PROFILE
  1830. if 'USERDNSDOMAIN' in os.environ and re.match(r'^\w+\.\w+$', os.environ['USERDNSDOMAIN']):
  1831. self.CONFIG.set(hosts_section, '.' + os.environ['USERDNSDOMAIN'], '')
  1832. self.HOST_MAP = collections.OrderedDict((k, v or k) for k, v in self.CONFIG.items(hosts_section) if '\\' not in k and ':' not in k and not k.startswith('.'))
  1833. self.HOST_POSTFIX_MAP = collections.OrderedDict((k, v) for k, v in self.CONFIG.items(hosts_section) if '\\' not in k and ':' not in k and k.startswith('.'))
  1834. self.HOST_POSTFIX_ENDSWITH = tuple(self.HOST_POSTFIX_MAP)
  1835. self.HOSTPORT_MAP = collections.OrderedDict((k, v) for k, v in self.CONFIG.items(hosts_section) if ':' in k and not k.startswith('.'))
  1836. self.HOSTPORT_POSTFIX_MAP = collections.OrderedDict((k, v) for k, v in self.CONFIG.items(hosts_section) if ':' in k and k.startswith('.'))
  1837. self.HOSTPORT_POSTFIX_ENDSWITH = tuple(self.HOSTPORT_POSTFIX_MAP)
  1838. self.URLRE_MAP = collections.OrderedDict((re.compile(k).match, v) for k, v in self.CONFIG.items(hosts_section) if '\\' in k)
  1839. self.HTTP_WITHGAE = set(self.CONFIG.get(http_section, 'withgae').split('|'))
  1840. self.HTTP_CRLFSITES = tuple(self.CONFIG.get(http_section, 'crlfsites').split('|'))
  1841. self.HTTP_FORCEHTTPS = tuple(self.CONFIG.get(http_section, 'forcehttps').split('|')) if self.CONFIG.get(http_section, 'forcehttps').strip() else tuple()
  1842. self.HTTP_NOFORCEHTTPS = set(self.CONFIG.get(http_section, 'noforcehttps').split('|')) if self.CONFIG.get(http_section, 'noforcehttps').strip() else set()
  1843. self.HTTP_FAKEHTTPS = tuple(self.CONFIG.get(http_section, 'fakehttps').split('|')) if self.CONFIG.get(http_section, 'fakehttps').strip() else tuple()
  1844. self.HTTP_NOFAKEHTTPS = set(self.CONFIG.get(http_section, 'nofakehttps').split('|')) if self.CONFIG.get(http_section, 'nofakehttps').strip() else set()
  1845. self.HTTP_DNS = self.CONFIG.get(http_section, 'dns').split('|') if self.CONFIG.has_option(http_section, 'dns') else []
  1846. self.IPLIST_MAP = collections.OrderedDict((k, v.split('|')) for k, v in self.CONFIG.items('iplist'))
  1847. self.IPLIST_MAP.update((k, [k]) for k, v in self.HOST_MAP.items() if k == v)
  1848. self.PAC_ENABLE = self.CONFIG.getint('pac', 'enable')
  1849. self.PAC_IP = self.CONFIG.get('pac', 'ip')
  1850. self.PAC_PORT = self.CONFIG.getint('pac', 'port')
  1851. self.PAC_FILE = self.CONFIG.get('pac', 'file').lstrip('/')
  1852. self.PAC_GFWLIST = self.CONFIG.get('pac', 'gfwlist')
  1853. self.PAC_ADBLOCK = self.CONFIG.get('pac', 'adblock')
  1854. self.PAC_ADMODE = self.CONFIG.getint('pac', 'admode')
  1855. self.PAC_EXPIRED = self.CONFIG.getint('pac', 'expired')
  1856. self.PHP_ENABLE = self.CONFIG.getint('php', 'enable')
  1857. self.PHP_LISTEN = self.CONFIG.get('php', 'listen')
  1858. self.PHP_PASSWORD = self.CONFIG.get('php', 'password') if self.CONFIG.has_option('php', 'password') else ''
  1859. self.PHP_CRLF = self.CONFIG.getint('php', 'crlf') if self.CONFIG.has_option('php', 'crlf') else 1
  1860. self.PHP_VALIDATE = self.CONFIG.getint('php', 'validate') if self.CONFIG.has_option('php', 'validate') else 0
  1861. self.PHP_FETCHSERVER = self.CONFIG.get('php', 'fetchserver')
  1862. self.PHP_USEHOSTS = self.CONFIG.getint('php', 'usehosts')
  1863. self.PROXY_ENABLE = self.CONFIG.getint('proxy', 'enable')
  1864. self.PROXY_AUTODETECT = self.CONFIG.getint('proxy', 'autodetect') if self.CONFIG.has_option('proxy', 'autodetect') else 0
  1865. self.PROXY_HOST = self.CONFIG.get('proxy', 'host')
  1866. self.PROXY_PORT = self.CONFIG.getint('proxy', 'port')
  1867. self.PROXY_USERNAME = self.CONFIG.get('proxy', 'username')
  1868. self.PROXY_PASSWROD = self.CONFIG.get('proxy', 'password')
  1869. if not self.PROXY_ENABLE and self.PROXY_AUTODETECT:
  1870. system_proxy = ProxyUtil.get_system_proxy()
  1871. if system_proxy and self.LISTEN_IP not in system_proxy:
  1872. _, username, password, address = ProxyUtil.parse_proxy(system_proxy)
  1873. proxyhost, _, proxyport = address.rpartition(':')
  1874. self.PROXY_ENABLE = 1
  1875. self.PROXY_USERNAME = username
  1876. self.PROXY_PASSWROD = password
  1877. self.PROXY_HOST = proxyhost
  1878. self.PROXY_PORT = int(proxyport)
  1879. if self.PROXY_ENABLE:
  1880. self.GAE_MODE = 'https'
  1881. self.AUTORANGE_HOSTS = self.CONFIG.get('autorange', 'hosts').split('|')
  1882. self.AUTORANGE_HOSTS_MATCH = [re.compile(fnmatch.translate(h)).match for h in self.AUTORANGE_HOSTS]
  1883. self.AUTORANGE_ENDSWITH = tuple(self.CONFIG.get('autorange', 'endswith').split('|'))
  1884. self.AUTORANGE_NOENDSWITH = tuple(self.CONFIG.get('autorange', 'noendswith').split('|'))
  1885. self.AUTORANGE_MAXSIZE = self.CONFIG.getint('autorange', 'maxsize')
  1886. self.AUTORANGE_WAITSIZE = self.CONFIG.getint('autorange', 'waitsize')
  1887. self.AUTORANGE_BUFSIZE = self.CONFIG.getint('autorange', 'bufsize')
  1888. self.AUTORANGE_THREADS = self.CONFIG.getint('autorange', 'threads')
  1889. self.FETCHMAX_LOCAL = self.CONFIG.getint('fetchmax', 'local') if self.CONFIG.get('fetchmax', 'local') else 3
  1890. self.FETCHMAX_SERVER = self.CONFIG.get('fetchmax', 'server')
  1891. self.DNS_ENABLE = self.CONFIG.getint('dns', 'enable')
  1892. self.DNS_LISTEN = self.CONFIG.get('dns', 'listen')
  1893. self.DNS_SERVERS = self.HTTP_DNS or self.CONFIG.get('dns', 'servers').split('|')
  1894. self.DNS_BLACKLIST = set(self.CONFIG.get('dns', 'blacklist').split('|'))
  1895. self.DNS_TCPOVER = tuple(self.CONFIG.get('dns', 'tcpover').split('|')) if self.CONFIG.get('dns', 'tcpover').strip() else tuple()
  1896. self.USERAGENT_ENABLE = self.CONFIG.getint('useragent', 'enable')
  1897. self.USERAGENT_STRING = self.CONFIG.get('useragent', 'string')
  1898. self.LOVE_ENABLE = self.CONFIG.getint('love', 'enable')
  1899. self.LOVE_TIP = self.CONFIG.get('love', 'tip').encode('utf8').decode('unicode-escape').split('|')
  1900. def extend_iplist(self, iplist_name, hosts):
  1901. logging.info('extend_iplist start for hosts=%s', hosts)
  1902. new_iplist = []
  1903. def do_remote_resolve(host, dnsserver, queue):
  1904. assert isinstance(dnsserver, basestring)
  1905. for dnslib_resolve in (dnslib_resolve_over_udp, dnslib_resolve_over_tcp):
  1906. try:
  1907. time.sleep(random.random())
  1908. iplist = dnslib_record2iplist(dnslib_resolve(host, [dnsserver], timeout=4, blacklist=self.DNS_BLACKLIST))
  1909. queue.put((host, dnsserver, iplist))
  1910. except (socket.error, OSError) as e:
  1911. logging.warning('%r remote host=%r failed: %s', dnslib_resolve, host, e)
  1912. time.sleep(1)
  1913. result_queue = Queue.Queue()
  1914. for host in hosts:
  1915. for dnsserver in self.DNS_SERVERS:
  1916. logging.debug('remote resolve host=%r from dnsserver=%r', host, dnsserver)
  1917. thread.start_new_thread(do_remote_resolve, (host, dnsserver, result_queue))
  1918. for _ in xrange(len(self.DNS_SERVERS) * len(hosts) * 2):
  1919. try:
  1920. host, dnsserver, iplist = result_queue.get(timeout=16)
  1921. logging.debug('%r remote host=%r return %s', dnsserver, host, iplist)
  1922. if host.endswith('.google.com'):
  1923. iplist = [x for x in iplist if is_google_ip(x)]
  1924. new_iplist += iplist
  1925. except Queue.Empty:
  1926. break
  1927. logging.info('extend_iplist finished, added %s', len(set(self.IPLIST_MAP[iplist_name])-set(new_iplist)))
  1928. self.IPLIST_MAP[iplist_name] = list(set(self.IPLIST_MAP[iplist_name] + new_iplist))
  1929. def resolve_iplist(self):
  1930. # https://support.google.com/websearch/answer/186669?hl=zh-Hans
  1931. def do_local_resolve(host, queue):
  1932. assert isinstance(host, basestring)
  1933. for _ in xrange(3):
  1934. try:
  1935. queue.put((host, socket.gethostbyname_ex(host)[-1]))
  1936. except (socket.error, OSError) as e:
  1937. logging.warning('socket.gethostbyname_ex host=%r failed: %s', host, e)
  1938. time.sleep(0.1)
  1939. google_blacklist = ['216.239.32.20'] + list(self.DNS_BLACKLIST)
  1940. for name, need_resolve_hosts in list(self.IPLIST_MAP.items()):
  1941. if all(re.match(r'\d+\.\d+\.\d+\.\d+', x) or ':' in x for x in need_resolve_hosts):
  1942. continue
  1943. need_resolve_remote = [x for x in need_resolve_hosts if ':' not in x and not re.match(r'\d+\.\d+\.\d+\.\d+', x)]
  1944. resolved_iplist = [x for x in need_resolve_hosts if x not in need_resolve_remote]
  1945. result_queue = Queue.Queue()
  1946. for host in need_resolve_remote:
  1947. logging.debug('local resolve host=%r', host)
  1948. thread.start_new_thread(do_local_resolve, (host, result_queue))
  1949. for _ in xrange(len(need_resolve_remote)):
  1950. try:
  1951. host, iplist = result_queue.get(timeout=8)
  1952. if host.endswith('.google.com'):
  1953. iplist = [x for x in iplist if is_google_ip(x)]
  1954. resolved_iplist += iplist
  1955. except Queue.Empty:
  1956. break
  1957. if name == 'google_hk':
  1958. for delay in (1, 60, 150, 240, 300, 450, 600, 900):
  1959. spawn_later(delay, self.extend_iplist, name, need_resolve_remote)
  1960. if name.startswith('google_') and name not in ('google_cn', 'google_hk') and resolved_iplist:
  1961. iplist_prefix = re.split(r'[\.:]', resolved_iplist[0])[0]
  1962. resolved_iplist = list(set(x for x in resolved_iplist if x.startswith(iplist_prefix)))
  1963. else:
  1964. resolved_iplist = list(set(resolved_iplist))
  1965. if name.startswith('google_'):
  1966. resolved_iplist = list(set(resolved_iplist) - set(google_blacklist))
  1967. if len(resolved_iplist) == 0:
  1968. logging.error('resolve %s host return empty! please retry!', name)
  1969. sys.exit(-1)
  1970. logging.info('resolve name=%s host to iplist=%r', name, resolved_iplist)
  1971. common.IPLIST_MAP[name] = resolved_iplist
  1972. def info(self):
  1973. info = ''
  1974. info += '------------------------------------------------------\n'
  1975. info += 'GoAgent Version : %s (python/%s %spyopenssl/%s)\n' % (__version__, sys.version[:5], gevent and 'gevent/%s ' % gevent.__version__ or '', getattr(OpenSSL, '__version__', 'Disabled'))
  1976. info += 'Uvent Version : %s (pyuv/%s libuv/%s)\n' % (__import__('uvent').__version__, __import__('pyuv').__version__, __import__('pyuv').LIBUV_VERSION) if all(x in sys.modules for x in ('pyuv', 'uvent')) else ''
  1977. info += 'Listen Address : %s:%d\n' % (self.LISTEN_IP, self.LISTEN_PORT)
  1978. info += 'Local Proxy : %s:%s\n' % (self.PROXY_HOST, self.PROXY_PORT) if self.PROXY_ENABLE else ''
  1979. info += 'Debug INFO : %s\n' % self.LISTEN_DEBUGINFO if self.LISTEN_DEBUGINFO else ''
  1980. info += 'GAE Mode : %s\n' % self.GAE_MODE
  1981. info += 'GAE Profile : %s\n' % self.GAE_PROFILE if self.GAE_PROFILE else ''
  1982. info += 'GAE APPID : %s\n' % '|'.join(self.GAE_APPIDS)
  1983. info += 'GAE Validate : %s\n' % self.GAE_VALIDATE if self.GAE_VALIDATE else ''
  1984. info += 'GAE Obfuscate : %s\n' % self.GAE_OBFUSCATE if self.GAE_OBFUSCATE else ''
  1985. if common.PAC_ENABLE:
  1986. info += 'Pac Server : http://%s:%d/%s\n' % (self.PAC_IP if self.PAC_IP and self.PAC_IP != '0.0.0.0' else ProxyUtil.get_listen_ip(), self.PAC_PORT, self.PAC_FILE)
  1987. info += 'Pac File : file://%s\n' % os.path.abspath(self.PAC_FILE)
  1988. if common.PHP_ENABLE:
  1989. info += 'PHP Listen : %s\n' % common.PHP_LISTEN
  1990. info += 'PHP FetchServer : %s\n' % common.PHP_FETCHSERVER
  1991. if common.DNS_ENABLE:
  1992. info += 'DNS Listen : %s\n' % common.DNS_LISTEN
  1993. info += 'DNS Servers : %s\n' % '|'.join(common.DNS_SERVERS)
  1994. info += '------------------------------------------------------\n'
  1995. return info
  1996. common = Common()
  1997. def message_html(title, banner, detail=''):
  1998. MESSAGE_TEMPLATE = '''
  1999. <html><head>
  2000. <meta http-equiv="content-type" content="text/html;charset=utf-8">
  2001. <title>$title</title>
  2002. <style><!--
  2003. body {font-family: arial,sans-serif}
  2004. div.nav {margin-top: 1ex}
  2005. div.nav A {font-size: 10pt; font-family: arial,sans-serif}
  2006. span.nav {font-size: 10pt; font-family: arial,sans-serif; font-weight: bold}
  2007. div.nav A,span.big {font-size: 12pt; color: #0000cc}
  2008. div.nav A {font-size: 10pt; color: black}
  2009. A.l:link {color: #6f6f6f}
  2010. A.u:link {color: green}
  2011. //--></style>
  2012. </head>
  2013. <body text=#000000 bgcolor=#ffffff>
  2014. <table border=0 cellpadding=2 cellspacing=0 width=100%>
  2015. <tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff><b>Message From LocalProxy</b></td></tr>
  2016. <tr><td> </td></tr></table>
  2017. <blockquote>
  2018. <H1>$banner</H1>
  2019. $detail
  2020. <p>
  2021. </blockquote>
  2022. <table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc><img alt="" width=1 height=4></td></tr></table>
  2023. </body></html>
  2024. '''
  2025. return string.Template(MESSAGE_TEMPLATE).substitute(title=title, banner=banner, detail=detail)
  2026. try:
  2027. from Crypto.Cipher.ARC4 import new as RC4Cipher
  2028. except ImportError:
  2029. logging.warn('Load Crypto.Cipher.ARC4 Failed, Use Pure Python Instead.')
  2030. class RC4Cipher(object):
  2031. def __init__(self, key):
  2032. x = 0
  2033. box = range(256)
  2034. for i, y in enumerate(box):
  2035. x = (x + y + ord(key[i % len(key)])) & 0xff
  2036. box[i], box[x] = box[x], y
  2037. self.__box = box
  2038. self.__x = 0
  2039. self.__y = 0
  2040. def encrypt(self, data):
  2041. out = []
  2042. out_append = out.append
  2043. x = self.__x
  2044. y = self.__y
  2045. box = self.__box
  2046. for char in data:
  2047. x = (x + 1) & 0xff
  2048. y = (y + box[x]) & 0xff
  2049. box[x], box[y] = box[y], box[x]
  2050. out_append(chr(ord(char) ^ box[(box[x] + box[y]) & 0xff]))
  2051. self.__x = x
  2052. self.__y = y
  2053. return ''.join(out)
  2054. class XORCipher(object):
  2055. """XOR Cipher Class"""
  2056. def __init__(self, key):
  2057. self.__key_gen = itertools.cycle([ord(x) for x in key]).next
  2058. self.__key_xor = lambda s: ''.join(chr(ord(x) ^ self.__key_gen()) for x in s)
  2059. if len(key) == 1:
  2060. try:
  2061. from Crypto.Util.strxor import strxor_c
  2062. c = ord(key)
  2063. self.__key_xor = lambda s: strxor_c(s, c)
  2064. except ImportError:
  2065. sys.stderr.write('Load Crypto.Util.strxor Failed, Use Pure Python Instead.\n')
  2066. def encrypt(self, data):
  2067. return self.__key_xor(data)
  2068. class CipherFileObject(object):
  2069. """fileobj wrapper for cipher"""
  2070. def __init__(self, fileobj, cipher):
  2071. self.__fileobj = fileobj
  2072. self.__cipher = cipher
  2073. def __getattr__(self, attr):
  2074. if attr not in ('__fileobj', '__cipher'):
  2075. return getattr(self.__fileobj, attr)
  2076. def read(self, size=-1):
  2077. return self.__cipher.encrypt(self.__fileobj.read(size))
  2078. class LocalProxyServer(SocketServer.ThreadingTCPServer):
  2079. """Local Proxy Server"""
  2080. allow_reuse_address = True
  2081. daemon_threads = True
  2082. def close_request(self, request):
  2083. try:
  2084. request.close()
  2085. except StandardError:
  2086. pass
  2087. def finish_request(self, request, client_address):
  2088. try:
  2089. self.RequestHandlerClass(request, client_address, self)
  2090. except NetWorkIOError as e:
  2091. if e[0] not in (errno.ECONNABORTED, errno.ECONNRESET, errno.EPIPE):
  2092. raise
  2093. def handle_error(self, *args):
  2094. """make ThreadingTCPServer happy"""
  2095. exc_info = sys.exc_info()
  2096. error = exc_info and len(exc_info) and exc_info[1]
  2097. if isinstance(error, NetWorkIOError) and len(error.args) > 1 and 'bad write retry' in error.args[1]:
  2098. exc_info = error = None
  2099. else:
  2100. del exc_info, error
  2101. SocketServer.ThreadingTCPServer.handle_error(self, *args)
  2102. class UserAgentFilter(BaseProxyHandlerFilter):
  2103. """user agent filter"""
  2104. def __init__(self, user_agent):
  2105. self.user_agent = user_agent
  2106. def filter(self, handler):
  2107. handler.headers['User-Agent'] = self.user_agent
  2108. class WithGAEFilter(BaseProxyHandlerFilter):
  2109. """with gae filter"""
  2110. def __init__(self, withgae_sites):
  2111. self.withgae_sites = set(withgae_sites)
  2112. def filter(self, handler):
  2113. if handler.host in self.withgae_sites:
  2114. logging.debug('WithGAEFilter metched %r %r', handler.path, handler.headers)
  2115. # assume the last one handler is GAEFetchFilter
  2116. return handler.handler_filters[-1].filter(handler)
  2117. class ForceHttpsFilter(BaseProxyHandlerFilter):
  2118. """force https filter"""
  2119. def __init__(self, forcehttps_sites, noforcehttps_sites):
  2120. self.forcehttps_sites = tuple(forcehttps_sites)
  2121. self.noforcehttps_sites = set(noforcehttps_sites)
  2122. def filter(self, handler):
  2123. if handler.command != 'CONNECT' and handler.host.endswith(self.forcehttps_sites) and handler.host not in self.noforcehttps_sites:
  2124. if not handler.headers.get('Referer', '').startswith('https://') and not handler.path.startswith('https://'):
  2125. logging.debug('ForceHttpsFilter metched %r %r', handler.path, handler.headers)
  2126. headers = {'Location': handler.path.replace('http://', 'https://', 1), 'Connection': 'close'}
  2127. return [handler.MOCK, 301, headers, '']
  2128. class FakeHttpsFilter(BaseProxyHandlerFilter):
  2129. """fake https filter"""
  2130. def __init__(self, fakehttps_sites, nofakehttps_sites):
  2131. self.fakehttps_sites = tuple(fakehttps_sites)
  2132. self.nofakehttps_sites = set(nofakehttps_sites)
  2133. def filter(self, handler):
  2134. if handler.command == 'CONNECT' and handler.host.endswith(self.fakehttps_sites) and handler.host not in self.nofakehttps_sites:
  2135. logging.debug('FakeHttpsFilter metched %r %r', handler.path, handler.headers)
  2136. return [handler.STRIP, True, None]
  2137. class URLRewriteFilter(BaseProxyHandlerFilter):
  2138. """url rewrite filter"""
  2139. rules = {
  2140. 'www.google.com': (r'^https?://www\.google\.com/url\?.*url=([^&]+)', lambda m: urllib.unquote_plus(m.group(1))),
  2141. 'www.google.com.hk': (r'^https?://www\.google\.com\.hk/url\?.*url=([^&]+)', lambda m: urllib.unquote_plus(m.group(1))),
  2142. }
  2143. def filter(self, handler):
  2144. if handler.host in self.rules:
  2145. pattern, callback = self.rules[handler.host]
  2146. m = re.search(pattern, handler.path)
  2147. if m:
  2148. logging.debug('URLRewriteFilter metched %r', handler.path)
  2149. headers = {'Location': callback(m), 'Connection': 'close'}
  2150. return [handler.MOCK, 301, headers, '']
  2151. class HostsFilter(BaseProxyHandlerFilter):
  2152. """force https filter"""
  2153. def filter_localfile(self, handler, filename):
  2154. content_type = None
  2155. try:
  2156. import mimetypes
  2157. content_type = mimetypes.types_map.get(os.path.splitext(filename)[1])
  2158. except StandardError as e:
  2159. logging.error('import mimetypes failed: %r', e)
  2160. try:
  2161. with open(filename, 'rb') as fp:
  2162. data = fp.read()
  2163. headers = {'Connection': 'close', 'Content-Length': str(len(data))}
  2164. if content_type:
  2165. headers['Content-Type'] = content_type
  2166. return [handler.MOCK, 200, headers, data]
  2167. except StandardError as e:
  2168. return [handler.MOCK, 403, {'Connection': 'close'}, 'read %r %r' % (filename, e)]
  2169. def filter(self, handler):
  2170. host, port = handler.host, handler.port
  2171. hostport = handler.path if handler.command == 'CONNECT' else '%s:%d' % (host, port)
  2172. hostname = ''
  2173. if host in common.HOST_MAP:
  2174. hostname = common.HOST_MAP[host] or host
  2175. elif host.endswith(common.HOST_POSTFIX_ENDSWITH):
  2176. hostname = next(common.HOST_POSTFIX_MAP[x] for x in common.HOST_POSTFIX_MAP if host.endswith(x)) or host
  2177. common.HOST_MAP[host] = hostname
  2178. if hostport in common.HOSTPORT_MAP:
  2179. hostname = common.HOSTPORT_MAP[hostport] or host
  2180. elif hostport.endswith(common.HOSTPORT_POSTFIX_ENDSWITH):
  2181. hostname = next(common.HOSTPORT_POSTFIX_MAP[x] for x in common.HOSTPORT_POSTFIX_MAP if hostport.endswith(x)) or host
  2182. common.HOSTPORT_MAP[hostport] = hostname
  2183. if handler.command != 'CONNECT' and common.URLRE_MAP:
  2184. try:
  2185. hostname = next(common.URLRE_MAP[x] for x in common.URLRE_MAP if x(handler.path)) or host
  2186. except StopIteration:
  2187. pass
  2188. if not hostname:
  2189. return None
  2190. elif hostname in common.IPLIST_MAP:
  2191. handler.dns_cache[host] = common.IPLIST_MAP[hostname]
  2192. elif hostname == host and host.endswith(common.DNS_TCPOVER) and host not in handler.dns_cache:
  2193. try:
  2194. iplist = dnslib_record2iplist(dnslib_resolve_over_tcp(host, handler.dns_servers, timeout=4, blacklist=handler.dns_blacklist))
  2195. logging.info('HostsFilter dnslib_resolve_over_tcp %r with %r return %s', host, handler.dns_servers, iplist)
  2196. handler.dns_cache[host] = iplist
  2197. except socket.error as e:
  2198. logging.warning('HostsFilter dnslib_resolve_over_tcp %r with %r failed: %r', host, handler.dns_servers, e)
  2199. elif re.match(r'^\d+\.\d+\.\d+\.\d+$', hostname) or ':' in hostname:
  2200. handler.dns_cache[host] = [hostname]
  2201. elif hostname.startswith('file://'):
  2202. filename = hostname.lstrip('file://')
  2203. if os.name == 'nt':
  2204. filename = filename.lstrip('/')
  2205. return self.filter_localfile(handler, filename)
  2206. cache_key = '%s:%s' % (hostname, port)
  2207. if handler.command == 'CONNECT':
  2208. return [handler.FORWARD, host, port, handler.connect_timeout, {'cache_key': cache_key}]
  2209. else:
  2210. if host.endswith(common.HTTP_CRLFSITES):
  2211. handler.close_connection = True
  2212. return [handler.DIRECT, {'crlf': True}]
  2213. else:
  2214. return [handler.DIRECT, {'cache_key': cache_key}]
  2215. class DirectRegionFilter(BaseProxyHandlerFilter):
  2216. """direct region filter"""
  2217. geoip = pygeoip.GeoIP(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'GeoIP.dat')) if pygeoip and common.GAE_REGIONS else None
  2218. region_cache = LRUCache(16*1024)
  2219. def __init__(self, regions):
  2220. self.regions = set(regions)
  2221. def get_country_code(self, hostname, dnsservers):
  2222. """http://dev.maxmind.com/geoip/legacy/codes/iso3166/"""
  2223. try:
  2224. return self.region_cache[hostname]
  2225. except KeyError:
  2226. pass
  2227. try:
  2228. if re.match(r'^\d+\.\d+\.\d+\.\d+$', hostname) or ':' in hostname:
  2229. iplist = [hostname]
  2230. elif dnsservers:
  2231. iplist = dnslib_record2iplist(dnslib_resolve_over_udp(hostname, dnsservers, timeout=2))
  2232. else:
  2233. iplist = socket.gethostbyname_ex(hostname)[-1]
  2234. country_code = self.geoip.country_code_by_addr(iplist[0])
  2235. except StandardError as e:
  2236. logging.warning('DirectRegionFilter cannot determine region for hostname=%r %r', hostname, e)
  2237. country_code = ''
  2238. self.region_cache[hostname] = country_code
  2239. return country_code
  2240. def filter(self, handler):
  2241. if self.geoip:
  2242. country_code = self.get_country_code(handler.host, handler.dns_servers)
  2243. if country_code in self.regions:
  2244. if handler.command == 'CONNECT':
  2245. return [handler.FORWARD, handler.host, handler.port, handler.connect_timeout]
  2246. else:
  2247. return [handler.DIRECT, {}]
  2248. class AutoRangeFilter(BaseProxyHandlerFilter):
  2249. """force https filter"""
  2250. def filter(self, handler):
  2251. path = urlparse.urlsplit(handler.path).path
  2252. need_autorange = any(x(handler.host) for x in common.AUTORANGE_HOSTS_MATCH) or path.endswith(common.AUTORANGE_ENDSWITH)
  2253. if path.endswith(common.AUTORANGE_NOENDSWITH) or 'range=' in urlparse.urlsplit(path).query or handler.command == 'HEAD':
  2254. need_autorange = False
  2255. if handler.command != 'HEAD' and handler.headers.get('Range'):
  2256. m = re.search(r'bytes=(\d+)-', handler.headers['Range'])
  2257. start = int(m.group(1) if m else 0)
  2258. handler.headers['Range'] = 'bytes=%d-%d' % (start, start+common.AUTORANGE_MAXSIZE-1)
  2259. logging.info('autorange range=%r match url=%r', handler.headers['Range'], handler.path)
  2260. elif need_autorange:
  2261. logging.info('Found [autorange]endswith match url=%r', handler.path)
  2262. m = re.search(r'bytes=(\d+)-', handler.headers.get('Range', ''))
  2263. start = int(m.group(1) if m else 0)
  2264. handler.headers['Range'] = 'bytes=%d-%d' % (start, start+common.AUTORANGE_MAXSIZE-1)
  2265. class GAEFetchFilter(BaseProxyHandlerFilter):
  2266. """force https filter"""
  2267. def filter(self, handler):
  2268. """https://developers.google.com/appengine/docs/python/urlfetch/"""
  2269. if handler.command == 'CONNECT':
  2270. do_ssl_handshake = 440 <= handler.port <= 450 or 1024 <= handler.port <= 65535
  2271. return [handler.STRIP, do_ssl_handshake, self if not common.URLRE_MAP else None]
  2272. elif handler.command in ('GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH'):
  2273. kwargs = {}
  2274. if common.GAE_PASSWORD:
  2275. kwargs['password'] = common.GAE_PASSWORD
  2276. if common.GAE_VALIDATE:
  2277. kwargs['validate'] = 1
  2278. fetchservers = ['%s://%s.appspot.com%s' % (common.GAE_MODE, x, common.GAE_PATH) for x in common.GAE_APPIDS]
  2279. return [handler.URLFETCH, fetchservers, common.FETCHMAX_LOCAL, kwargs]
  2280. else:
  2281. if common.PHP_ENABLE:
  2282. return PHPProxyHandler.handler_filters[-1].filter(handler)
  2283. else:
  2284. logging.warning('"%s %s" not supported by GAE, please enable PHP mode!', handler.command, handler.host)
  2285. return [handler.DIRECT, {}]
  2286. class GAEProxyHandler(AdvancedProxyHandler):
  2287. """GAE Proxy Handler"""
  2288. handler_filters = [GAEFetchFilter()]
  2289. def first_run(self):
  2290. """GAEProxyHandler setup, init domain/iplist map"""
  2291. if not common.PROXY_ENABLE:
  2292. logging.info('resolve common.IPLIST_MAP names=%s to iplist', list(common.IPLIST_MAP))
  2293. common.resolve_iplist()
  2294. random.shuffle(common.GAE_APPIDS)
  2295. def gethostbyname2(self, hostname):
  2296. for postfix in ('.appspot.com', '.googleusercontent.com'):
  2297. if hostname.endswith(postfix):
  2298. host = common.HOST_MAP.get(hostname) or common.HOST_POSTFIX_MAP[postfix]
  2299. return common.IPLIST_MAP.get(host) or host.split('|')
  2300. return AdvancedProxyHandler.gethostbyname2(self, hostname)
  2301. def handle_urlfetch_error(self, fetchserver, response):
  2302. gae_appid = urlparse.urlsplit(fetchserver).hostname.split('.')[-3]
  2303. if response.app_status == 503:
  2304. # appid over qouta, switch to next appid
  2305. if gae_appid == common.GAE_APPIDS[0] and len(common.GAE_APPIDS) > 1:
  2306. common.GAE_APPIDS.append(common.GAE_APPIDS.pop(0))
  2307. logging.info('gae_appid=%r over qouta, switch next appid=%r', gae_appid, common.GAE_APPIDS[0])
  2308. class PHPFetchFilter(BaseProxyHandlerFilter):
  2309. """force https filter"""
  2310. def filter(self, handler):
  2311. if handler.command == 'CONNECT':
  2312. return [handler.STRIP, True, self]
  2313. else:
  2314. kwargs = {}
  2315. if common.PHP_PASSWORD:
  2316. kwargs['password'] = common.PHP_PASSWORD
  2317. if common.PHP_VALIDATE:
  2318. kwargs['validate'] = 1
  2319. return [handler.URLFETCH, [common.PHP_FETCHSERVER], 1, kwargs]
  2320. class PHPProxyHandler(AdvancedProxyHandler):
  2321. """PHP Proxy Handler"""
  2322. first_run_lock = threading.Lock()
  2323. handler_filters = [PHPFetchFilter()]
  2324. def first_run(self):
  2325. if common.PHP_USEHOSTS:
  2326. self.handler_filters.insert(-1, HostsFilter())
  2327. if not common.PROXY_ENABLE:
  2328. common.resolve_iplist()
  2329. fetchhost = urlparse.urlsplit(common.PHP_FETCHSERVER).hostname
  2330. logging.info('resolve common.PHP_FETCHSERVER domain=%r to iplist', fetchhost)
  2331. if common.PHP_USEHOSTS and fetchhost in common.HOST_MAP:
  2332. hostname = common.HOST_MAP[fetchhost]
  2333. fetchhost_iplist = sum([socket.gethostbyname_ex(x)[-1] for x in common.IPLIST_MAP.get(hostname) or hostname.split('|')], [])
  2334. else:
  2335. fetchhost_iplist = self.gethostbyname2(fetchhost)
  2336. if len(fetchhost_iplist) == 0:
  2337. logging.error('resolve %r domain return empty! please use ip list to replace domain list!', fetchhost)
  2338. sys.exit(-1)
  2339. self.dns_cache[fetchhost] = list(set(fetchhost_iplist))
  2340. logging.info('resolve common.PHP_FETCHSERVER domain to iplist=%r', fetchhost_iplist)
  2341. return True
  2342. class ProxyChainMixin:
  2343. """proxy chain mixin"""
  2344. def gethostbyname2(self, hostname):
  2345. try:
  2346. return socket.gethostbyname_ex(hostname)[-1]
  2347. except socket.error:
  2348. return [hostname]
  2349. def create_tcp_connection(self, hostname, port, timeout, **kwargs):
  2350. sock = socket.create_connection((common.PROXY_HOST, int(common.PROXY_PORT)))
  2351. if hostname.endswith('.appspot.com'):
  2352. hostname = 'www.google.com'
  2353. request_data = 'CONNECT %s:%s HTTP/1.1\r\n' % (hostname, port)
  2354. if common.PROXY_USERNAME and common.PROXY_PASSWROD:
  2355. request_data += 'Proxy-Authorization: Basic %s\r\n' % base64.b64encode(('%s:%s' % (common.PROXY_USERNAME, common.PROXY_PASSWROD)).encode()).decode().strip()
  2356. request_data += '\r\n'
  2357. sock.sendall(request_data)
  2358. response = httplib.HTTPResponse(sock)
  2359. response.fp.close()
  2360. response.fp = sock.makefile('rb', 0)
  2361. response.begin()
  2362. if response.status >= 400:
  2363. raise httplib.BadStatusLine('%s %s %s' % (response.version, response.status, response.reason))
  2364. return sock
  2365. def create_ssl_connection(self, hostname, port, timeout, **kwargs):
  2366. sock = self.create_tcp_connection(hostname, port, timeout, **kwargs)
  2367. ssl_sock = ssl.wrap_socket(sock)
  2368. return ssl_sock
  2369. class GreenForwardMixin:
  2370. """green forward mixin"""
  2371. @staticmethod
  2372. def io_copy(dest, source, timeout, bufsize):
  2373. try:
  2374. dest.settimeout(timeout)
  2375. source.settimeout(timeout)
  2376. while 1:
  2377. data = source.recv(bufsize)
  2378. if not data:
  2379. break
  2380. dest.sendall(data)
  2381. except socket.timeout:
  2382. pass
  2383. except NetWorkIOError as e:
  2384. if e.args[0] not in (errno.ECONNABORTED, errno.ECONNRESET, errno.ENOTCONN, errno.EPIPE):
  2385. raise
  2386. if e.args[0] in (errno.EBADF,):
  2387. return
  2388. finally:
  2389. for sock in (dest, source):
  2390. try:
  2391. sock.close()
  2392. except StandardError:
  2393. pass
  2394. def forward_socket(self, local, remote, timeout):
  2395. """forward socket"""
  2396. bufsize = self.bufsize
  2397. thread.start_new_thread(GreenForwardMixin.io_copy, (remote.dup(), local.dup(), timeout, bufsize))
  2398. GreenForwardMixin.io_copy(local, remote, timeout, bufsize)
  2399. class ProxyChainGAEProxyHandler(ProxyChainMixin, GAEProxyHandler):
  2400. pass
  2401. class ProxyChainPHPProxyHandler(ProxyChainMixin, PHPProxyHandler):
  2402. pass
  2403. class GreenForwardGAEProxyHandler(GreenForwardMixin, GAEProxyHandler):
  2404. pass
  2405. class GreenForwardPHPProxyHandler(GreenForwardMixin, PHPProxyHandler):
  2406. pass
  2407. class ProxyChainGreenForwardGAEProxyHandler(ProxyChainMixin, GreenForwardGAEProxyHandler):
  2408. pass
  2409. class ProxyChainGreenForwardPHPProxyHandler(ProxyChainMixin, GreenForwardPHPProxyHandler):
  2410. pass
  2411. def get_uptime():
  2412. if os.name == 'nt':
  2413. import ctypes
  2414. try:
  2415. tick = ctypes.windll.kernel32.GetTickCount64()
  2416. except AttributeError:
  2417. tick = ctypes.windll.kernel32.GetTickCount()
  2418. return tick / 1000.0
  2419. elif os.path.isfile('/proc/uptime'):
  2420. with open('/proc/uptime', 'rb') as fp:
  2421. uptime = fp.readline().strip().split()[0].strip()
  2422. return float(uptime)
  2423. elif any(os.path.isfile(os.path.join(x, 'uptime')) for x in os.environ['PATH'].split(os.pathsep)):
  2424. # http://www.opensource.apple.com/source/lldb/lldb-69/test/pexpect-2.4/examples/uptime.py
  2425. pattern = r'up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])'
  2426. output = os.popen('uptime').read()
  2427. duration, _, _, _, _ = re.search(pattern, output).groups()
  2428. days, hours, mins = 0, 0, 0
  2429. if 'day' in duration:
  2430. m = re.search(r'([0-9]+)\s+day', duration)
  2431. days = int(m.group(1))
  2432. if ':' in duration:
  2433. m = re.search(r'([0-9]+):([0-9]+)', duration)
  2434. hours = int(m.group(1))
  2435. mins = int(m.group(2))
  2436. if 'min' in duration:
  2437. m = re.search(r'([0-9]+)\s+min', duration)
  2438. mins = int(m.group(1))
  2439. return days * 86400 + hours * 3600 + mins * 60
  2440. else:
  2441. #TODO: support other platforms
  2442. return None
  2443. class PacUtil(object):
  2444. """GoAgent Pac Util"""
  2445. @staticmethod
  2446. def update_pacfile(filename):
  2447. listen_ip = '127.0.0.1'
  2448. autoproxy = '%s:%s' % (listen_ip, common.LISTEN_PORT)
  2449. blackhole = '%s:%s' % (listen_ip, common.PAC_PORT)
  2450. default = 'PROXY %s:%s' % (common.PROXY_HOST, common.PROXY_PORT) if common.PROXY_ENABLE else 'DIRECT'
  2451. opener = urllib2.build_opener(urllib2.ProxyHandler({'http': autoproxy, 'https': autoproxy}))
  2452. content = ''
  2453. need_update = True
  2454. with open(filename, 'rb') as fp:
  2455. content = fp.read()
  2456. try:
  2457. placeholder = '// AUTO-GENERATED RULES, DO NOT MODIFY!'
  2458. content = content[:content.index(placeholder)+len(placeholder)]
  2459. content = re.sub(r'''blackhole\s*=\s*['"]PROXY [\.\w:]+['"]''', 'blackhole = \'PROXY %s\'' % blackhole, content)
  2460. content = re.sub(r'''autoproxy\s*=\s*['"]PROXY [\.\w:]+['"]''', 'autoproxy = \'PROXY %s\'' % autoproxy, content)
  2461. content = re.sub(r'''defaultproxy\s*=\s*['"](DIRECT|PROXY [\.\w:]+)['"]''', 'defaultproxy = \'%s\'' % default, content)
  2462. content = re.sub(r'''host\s*==\s*['"][\.\w:]+['"]\s*\|\|\s*isPlainHostName''', 'host == \'%s\' || isPlainHostName' % listen_ip, content)
  2463. if content.startswith('//'):
  2464. line = '// Proxy Auto-Config file generated by autoproxy2pac, %s\r\n' % time.strftime('%Y-%m-%d %H:%M:%S')
  2465. content = line + '\r\n'.join(content.splitlines()[1:])
  2466. except ValueError:
  2467. need_update = False
  2468. try:
  2469. if common.PAC_ADBLOCK:
  2470. admode = common.PAC_ADMODE
  2471. logging.info('try download %r to update_pacfile(%r)', common.PAC_ADBLOCK, filename)
  2472. adblock_content = opener.open(common.PAC_ADBLOCK).read()
  2473. logging.info('%r downloaded, try convert it with adblock2pac', common.PAC_ADBLOCK)
  2474. if 'gevent' in sys.modules and time.sleep is getattr(sys.modules['gevent'], 'sleep', None) and hasattr(gevent.get_hub(), 'threadpool'):
  2475. jsrule = gevent.get_hub().threadpool.apply_e(Exception, PacUtil.adblock2pac, (adblock_content, 'FindProxyForURLByAdblock', blackhole, default, admode))
  2476. else:
  2477. jsrule = PacUtil.adblock2pac(adblock_content, 'FindProxyForURLByAdblock', blackhole, default, admode)
  2478. content += '\r\n' + jsrule + '\r\n'
  2479. logging.info('%r downloaded and parsed', common.PAC_ADBLOCK)
  2480. else:
  2481. content += '\r\nfunction FindProxyForURLByAdblock(url, host) {return "DIRECT";}\r\n'
  2482. except StandardError as e:
  2483. need_update = False
  2484. logging.exception('update_pacfile failed: %r', e)
  2485. try:
  2486. logging.info('try download %r to update_pacfile(%r)', common.PAC_GFWLIST, filename)
  2487. autoproxy_content = base64.b64decode(opener.open(common.PAC_GFWLIST).read())
  2488. logging.info('%r downloaded, try convert it with autoproxy2pac_lite', common.PAC_GFWLIST)
  2489. if 'gevent' in sys.modules and time.sleep is getattr(sys.modules['gevent'], 'sleep', None) and hasattr(gevent.get_hub(), 'threadpool'):
  2490. jsrule = gevent.get_hub().threadpool.apply_e(Exception, PacUtil.autoproxy2pac_lite, (autoproxy_content, 'FindProxyForURLByAutoProxy', autoproxy, default))
  2491. else:
  2492. jsrule = PacUtil.autoproxy2pac_lite(autoproxy_content, 'FindProxyForURLByAutoProxy', autoproxy, default)
  2493. content += '\r\n' + jsrule + '\r\n'
  2494. logging.info('%r downloaded and parsed', common.PAC_GFWLIST)
  2495. except StandardError as e:
  2496. need_update = False
  2497. logging.exception('update_pacfile failed: %r', e)
  2498. if need_update:
  2499. with open(filename, 'wb') as fp:
  2500. fp.write(content)
  2501. logging.info('%r successfully updated', filename)
  2502. @staticmethod
  2503. def autoproxy2pac(content, func_name='FindProxyForURLByAutoProxy', proxy='127.0.0.1:8087', default='DIRECT', indent=4):
  2504. """Autoproxy to Pac, based on https://github.com/iamamac/autoproxy2pac"""
  2505. jsLines = []
  2506. for line in content.splitlines()[1:]:
  2507. if line and not line.startswith("!"):
  2508. use_proxy = True
  2509. if line.startswith("@@"):
  2510. line = line[2:]
  2511. use_proxy = False
  2512. return_proxy = 'PROXY %s' % proxy if use_proxy else default
  2513. if line.startswith('/') and line.endswith('/'):
  2514. jsLine = 'if (/%s/i.test(url)) return "%s";' % (line[1:-1], return_proxy)
  2515. elif line.startswith('||'):
  2516. domain = line[2:].lstrip('.')
  2517. if len(jsLines) > 0 and ('host.indexOf(".%s") >= 0' % domain in jsLines[-1] or 'host.indexOf("%s") >= 0' % domain in jsLines[-1]):
  2518. jsLines.pop()
  2519. jsLine = 'if (dnsDomainIs(host, ".%s") || host == "%s") return "%s";' % (domain, domain, return_proxy)
  2520. elif line.startswith('|'):
  2521. jsLine = 'if (url.indexOf("%s") == 0) return "%s";' % (line[1:], return_proxy)
  2522. elif '*' in line:
  2523. jsLine = 'if (shExpMatch(url, "*%s*")) return "%s";' % (line.strip('*'), return_proxy)
  2524. elif '/' not in line:
  2525. jsLine = 'if (host.indexOf("%s") >= 0) return "%s";' % (line, return_proxy)
  2526. else:
  2527. jsLine = 'if (url.indexOf("%s") >= 0) return "%s";' % (line, return_proxy)
  2528. jsLine = ' ' * indent + jsLine
  2529. if use_proxy:
  2530. jsLines.append(jsLine)
  2531. else:
  2532. jsLines.insert(0, jsLine)
  2533. function = 'function %s(url, host) {\r\n%s\r\n%sreturn "%s";\r\n}' % (func_name, '\n'.join(jsLines), ' '*indent, default)
  2534. return function
  2535. @staticmethod
  2536. def autoproxy2pac_lite(content, func_name='FindProxyForURLByAutoProxy', proxy='127.0.0.1:8087', default='DIRECT', indent=4):
  2537. """Autoproxy to Pac, based on https://github.com/iamamac/autoproxy2pac"""
  2538. direct_domain_set = set([])
  2539. proxy_domain_set = set([])
  2540. for line in content.splitlines()[1:]:
  2541. if line and not line.startswith(('!', '|!', '||!')):
  2542. use_proxy = True
  2543. if line.startswith("@@"):
  2544. line = line[2:]
  2545. use_proxy = False
  2546. domain = ''
  2547. if line.startswith('/') and line.endswith('/'):
  2548. line = line[1:-1]
  2549. if line.startswith('^https?:\\/\\/[^\\/]+') and re.match(r'^(\w|\\\-|\\\.)+$', line[18:]):
  2550. domain = line[18:].replace(r'\.', '.')
  2551. else:
  2552. logging.warning('unsupport gfwlist regex: %r', line)
  2553. elif line.startswith('||'):
  2554. domain = line[2:].lstrip('*').rstrip('/')
  2555. elif line.startswith('|'):
  2556. domain = urlparse.urlsplit(line[1:]).hostname.lstrip('*')
  2557. elif line.startswith(('http://', 'https://')):
  2558. domain = urlparse.urlsplit(line).hostname.lstrip('*')
  2559. elif re.search(r'^([\w\-\_\.]+)([\*\/]|$)', line):
  2560. domain = re.split(r'[\*\/]', line)[0]
  2561. else:
  2562. pass
  2563. if '*' in domain:
  2564. domain = domain.split('*')[-1]
  2565. if not domain or re.match(r'^\w+$', domain):
  2566. logging.debug('unsupport gfwlist rule: %r', line)
  2567. continue
  2568. if use_proxy:
  2569. proxy_domain_set.add(domain)
  2570. else:
  2571. direct_domain_set.add(domain)
  2572. proxy_domain_list = sorted(set(x.lstrip('.') for x in proxy_domain_set))
  2573. autoproxy_host = ',\r\n'.join('%s"%s": 1' % (' '*indent, x) for x in proxy_domain_list)
  2574. template = '''\
  2575. var autoproxy_host = {
  2576. %(autoproxy_host)s
  2577. };
  2578. function %(func_name)s(url, host) {
  2579. var lastPos;
  2580. do {
  2581. if (autoproxy_host.hasOwnProperty(host)) {
  2582. return 'PROXY %(proxy)s';
  2583. }
  2584. lastPos = host.indexOf('.') + 1;
  2585. host = host.slice(lastPos);
  2586. } while (lastPos >= 1);
  2587. return '%(default)s';
  2588. }'''
  2589. template = re.sub(r'(?m)^\s{%d}' % min(len(re.search(r' +', x).group()) for x in template.splitlines()), '', template)
  2590. template_args = {'autoproxy_host': autoproxy_host,
  2591. 'func_name': func_name,
  2592. 'proxy': proxy,
  2593. 'default': default}
  2594. return template % template_args
  2595. @staticmethod
  2596. def urlfilter2pac(content, func_name='FindProxyForURLByUrlfilter', proxy='127.0.0.1:8086', default='DIRECT', indent=4):
  2597. """urlfilter.ini to Pac, based on https://github.com/iamamac/autoproxy2pac"""
  2598. jsLines = []
  2599. for line in content[content.index('[exclude]'):].splitlines()[1:]:
  2600. if line and not line.startswith(';'):
  2601. use_proxy = True
  2602. if line.startswith("@@"):
  2603. line = line[2:]
  2604. use_proxy = False
  2605. return_proxy = 'PROXY %s' % proxy if use_proxy else default
  2606. if '*' in line:
  2607. jsLine = 'if (shExpMatch(url, "%s")) return "%s";' % (line, return_proxy)
  2608. else:
  2609. jsLine = 'if (url == "%s") return "%s";' % (line, return_proxy)
  2610. jsLine = ' ' * indent + jsLine
  2611. if use_proxy:
  2612. jsLines.append(jsLine)
  2613. else:
  2614. jsLines.insert(0, jsLine)
  2615. function = 'function %s(url, host) {\r\n%s\r\n%sreturn "%s";\r\n}' % (func_name, '\n'.join(jsLines), ' '*indent, default)
  2616. return function
  2617. @staticmethod
  2618. def adblock2pac(content, func_name='FindProxyForURLByAdblock', proxy='127.0.0.1:8086', default='DIRECT', admode=1, indent=4):
  2619. """adblock list to Pac, based on https://github.com/iamamac/autoproxy2pac"""
  2620. white_conditions = {'host': [], 'url.indexOf': [], 'shExpMatch': []}
  2621. black_conditions = {'host': [], 'url.indexOf': [], 'shExpMatch': []}
  2622. for line in content.splitlines()[1:]:
  2623. if not line or line.startswith('!') or '##' in line or '#@#' in line:
  2624. continue
  2625. use_proxy = True
  2626. use_start = False
  2627. use_end = False
  2628. use_domain = False
  2629. use_postfix = []
  2630. if '$' in line:
  2631. posfixs = line.split('$')[-1].split(',')
  2632. if any('domain' in x for x in posfixs):
  2633. continue
  2634. if 'image' in posfixs:
  2635. use_postfix += ['.jpg', '.gif']
  2636. elif 'script' in posfixs:
  2637. use_postfix += ['.js']
  2638. else:
  2639. continue
  2640. line = line.split('$')[0]
  2641. if line.startswith("@@"):
  2642. line = line[2:]
  2643. use_proxy = False
  2644. if '||' == line[:2]:
  2645. line = line[2:]
  2646. if '/' not in line:
  2647. use_domain = True
  2648. else:
  2649. use_start = True
  2650. elif '|' == line[0]:
  2651. line = line[1:]
  2652. use_start = True
  2653. if line[-1] in ('^', '|'):
  2654. line = line[:-1]
  2655. if not use_postfix:
  2656. use_end = True
  2657. line = line.replace('^', '*').strip('*')
  2658. conditions = black_conditions if use_proxy else white_conditions
  2659. if use_start and use_end:
  2660. conditions['shExpMatch'] += ['*%s*' % line]
  2661. elif use_start:
  2662. if '*' in line:
  2663. if use_postfix:
  2664. conditions['shExpMatch'] += ['*%s*%s' % (line, x) for x in use_postfix]
  2665. else:
  2666. conditions['shExpMatch'] += ['*%s*' % line]
  2667. else:
  2668. conditions['url.indexOf'] += [line]
  2669. elif use_domain and use_end:
  2670. if '*' in line:
  2671. conditions['shExpMatch'] += ['%s*' % line]
  2672. else:
  2673. conditions['host'] += [line]
  2674. elif use_domain:
  2675. if line.split('/')[0].count('.') <= 1:
  2676. if use_postfix:
  2677. conditions['shExpMatch'] += ['*.%s*%s' % (line, x) for x in use_postfix]
  2678. else:
  2679. conditions['shExpMatch'] += ['*.%s*' % line]
  2680. else:
  2681. if '*' in line:
  2682. if use_postfix:
  2683. conditions['shExpMatch'] += ['*%s*%s' % (line, x) for x in use_postfix]
  2684. else:
  2685. conditions['shExpMatch'] += ['*%s*' % line]
  2686. else:
  2687. if use_postfix:
  2688. conditions['shExpMatch'] += ['*%s*%s' % (line, x) for x in use_postfix]
  2689. else:
  2690. conditions['url.indexOf'] += ['http://%s' % line]
  2691. else:
  2692. if use_postfix:
  2693. conditions['shExpMatch'] += ['*%s*%s' % (line, x) for x in use_postfix]
  2694. else:
  2695. conditions['shExpMatch'] += ['*%s*' % line]
  2696. templates = ['''\
  2697. function %(func_name)s(url, host) {
  2698. return '%(default)s';
  2699. }''',
  2700. '''\
  2701. var blackhole_host = {
  2702. %(blackhole_host)s
  2703. };
  2704. function %(func_name)s(url, host) {
  2705. // untrusted ablock plus list, disable whitelist until chinalist come back.
  2706. if (blackhole_host.hasOwnProperty(host)) {
  2707. return 'PROXY %(proxy)s';
  2708. }
  2709. return '%(default)s';
  2710. }''',
  2711. '''\
  2712. var blackhole_host = {
  2713. %(blackhole_host)s
  2714. };
  2715. var blackhole_url_indexOf = [
  2716. %(blackhole_url_indexOf)s
  2717. ];
  2718. function %s(url, host) {
  2719. // untrusted ablock plus list, disable whitelist until chinalist come back.
  2720. if (blackhole_host.hasOwnProperty(host)) {
  2721. return 'PROXY %(proxy)s';
  2722. }
  2723. for (i = 0; i < blackhole_url_indexOf.length; i++) {
  2724. if (url.indexOf(blackhole_url_indexOf[i]) >= 0) {
  2725. return 'PROXY %(proxy)s';
  2726. }
  2727. }
  2728. return '%(default)s';
  2729. }''',
  2730. '''\
  2731. var blackhole_host = {
  2732. %(blackhole_host)s
  2733. };
  2734. var blackhole_url_indexOf = [
  2735. %(blackhole_url_indexOf)s
  2736. ];
  2737. var blackhole_shExpMatch = [
  2738. %(blackhole_shExpMatch)s
  2739. ];
  2740. function %(func_name)s(url, host) {
  2741. // untrusted ablock plus list, disable whitelist until chinalist come back.
  2742. if (blackhole_host.hasOwnProperty(host)) {
  2743. return 'PROXY %(proxy)s';
  2744. }
  2745. for (i = 0; i < blackhole_url_indexOf.length; i++) {
  2746. if (url.indexOf(blackhole_url_indexOf[i]) >= 0) {
  2747. return 'PROXY %(proxy)s';
  2748. }
  2749. }
  2750. for (i = 0; i < blackhole_shExpMatch.length; i++) {
  2751. if (shExpMatch(url, blackhole_shExpMatch[i])) {
  2752. return 'PROXY %(proxy)s';
  2753. }
  2754. }
  2755. return '%(default)s';
  2756. }''']
  2757. template = re.sub(r'(?m)^\s{%d}' % min(len(re.search(r' +', x).group()) for x in templates[admode].splitlines()), '', templates[admode])
  2758. template_kwargs = {'blackhole_host': ',\r\n'.join("%s'%s': 1" % (' '*indent, x) for x in sorted(black_conditions['host'])),
  2759. 'blackhole_url_indexOf': ',\r\n'.join("%s'%s'" % (' '*indent, x) for x in sorted(black_conditions['url.indexOf'])),
  2760. 'blackhole_shExpMatch': ',\r\n'.join("%s'%s'" % (' '*indent, x) for x in sorted(black_conditions['shExpMatch'])),
  2761. 'func_name': func_name,
  2762. 'proxy': proxy,
  2763. 'default': default}
  2764. return template % template_kwargs
  2765. class PacFileFilter(BaseProxyHandlerFilter):
  2766. """pac file filter"""
  2767. def filter(self, handler):
  2768. is_local_client = handler.client_address[0] in ('127.0.0.1', '::1')
  2769. pacfile = os.path.join(os.path.dirname(os.path.abspath(__file__)), common.PAC_FILE)
  2770. urlparts = urlparse.urlsplit(handler.path)
  2771. if handler.command == 'GET' and urlparts.path.lstrip('/') == common.PAC_FILE:
  2772. if urlparts.query == 'flush':
  2773. if is_local_client:
  2774. thread.start_new_thread(PacUtil.update_pacfile, (pacfile,))
  2775. else:
  2776. return [handler.MOCK, 403, {'Content-Type': 'text/plain'}, 'client address %r not allowed' % handler.client_address[0]]
  2777. if time.time() - os.path.getmtime(pacfile) > common.PAC_EXPIRED:
  2778. # check system uptime > 30 minutes
  2779. uptime = get_uptime()
  2780. if uptime and uptime > 1800:
  2781. thread.start_new_thread(lambda: os.utime(pacfile, (time.time(), time.time())) or PacUtil.update_pacfile(pacfile), tuple())
  2782. with open(pacfile, 'rb') as fp:
  2783. content = fp.read()
  2784. if not is_local_client:
  2785. serving_addr = urlparts.hostname or ProxyUtil.get_listen_ip()
  2786. content = content.replace('127.0.0.1', serving_addr)
  2787. headers = {'Content-Type': 'text/plain'}
  2788. if 'gzip' in handler.headers.get('Accept-Encoding', ''):
  2789. headers['Content-Encoding'] = 'gzip'
  2790. compressobj = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
  2791. dataio = io.BytesIO()
  2792. dataio.write('\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff')
  2793. dataio.write(compressobj.compress(content))
  2794. dataio.write(compressobj.flush())
  2795. dataio.write(struct.pack('<LL', zlib.crc32(content) & 0xFFFFFFFFL, len(content) & 0xFFFFFFFFL))
  2796. content = dataio.getvalue()
  2797. return [handler.MOCK, 200, headers, content]
  2798. class StaticFileFilter(BaseProxyHandlerFilter):
  2799. """static file filter"""
  2800. index_file = 'index.html'
  2801. def format_index_html(self, dirname):
  2802. INDEX_TEMPLATE = u'''
  2803. <html>
  2804. <title>Directory listing for $dirname</title>
  2805. <body>
  2806. <h2>Directory listing for $dirname</h2>
  2807. <hr>
  2808. <ul>
  2809. $html
  2810. </ul>
  2811. <hr>
  2812. </body></html>
  2813. '''
  2814. html = ''
  2815. if not isinstance(dirname, unicode):
  2816. dirname = dirname.decode(sys.getfilesystemencoding())
  2817. for name in os.listdir(dirname):
  2818. fullname = os.path.join(dirname, name)
  2819. suffix = u'/' if os.path.isdir(fullname) else u''
  2820. html += u'<li><a href="%s%s">%s%s</a>\r\n' % (name, suffix, name, suffix)
  2821. return string.Template(INDEX_TEMPLATE).substitute(dirname=dirname, html=html)
  2822. def filter(self, handler):
  2823. path = urlparse.urlsplit(handler.path).path
  2824. if path.startswith('/'):
  2825. path = urllib.unquote_plus(path.lstrip('/') or '.').decode('utf8')
  2826. if os.path.isdir(path):
  2827. index_file = os.path.join(path, self.index_file)
  2828. if not os.path.isfile(index_file):
  2829. content = self.format_index_html(path).encode('UTF-8')
  2830. headers = {'Content-Type': 'text/html; charset=utf-8', 'Connection': 'close'}
  2831. return [handler.MOCK, 200, headers, content]
  2832. else:
  2833. path = index_file
  2834. if os.path.isfile(path):
  2835. content_type = 'application/octet-stream'
  2836. try:
  2837. import mimetypes
  2838. content_type = mimetypes.types_map.get(os.path.splitext(path)[1])
  2839. except StandardError as e:
  2840. logging.error('import mimetypes failed: %r', e)
  2841. with open(path, 'rb') as fp:
  2842. content = fp.read()
  2843. headers = {'Connection': 'close', 'Content-Type': content_type}
  2844. return [handler.MOCK, 200, headers, content]
  2845. class BlackholeFilter(BaseProxyHandlerFilter):
  2846. """blackhole filter"""
  2847. one_pixel_gif = 'GIF89a\x01\x00\x01\x00\x80\xff\x00\xc0\xc0\xc0\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;'
  2848. def filter(self, handler):
  2849. if handler.command == 'CONNECT':
  2850. return [handler.STRIP, True, self]
  2851. elif handler.path.startswith(('http://', 'https://')):
  2852. headers = {'Cache-Control': 'max-age=86400',
  2853. 'Expires': 'Oct, 01 Aug 2100 00:00:00 GMT',
  2854. 'Connection': 'close'}
  2855. content = ''
  2856. if urlparse.urlsplit(handler.path).path.lower().endswith(('.jpg', '.gif', '.png','.jpeg', '.bmp')):
  2857. headers['Content-Type'] = 'image/gif'
  2858. content = self.one_pixel_gif
  2859. return [handler.MOCK, 200, headers, content]
  2860. else:
  2861. return [handler.MOCK, 404, {'Connection': 'close'}, '']
  2862. class PACProxyHandler(SimpleProxyHandler):
  2863. """pac proxy handler"""
  2864. handler_filters = [PacFileFilter(), StaticFileFilter(), BlackholeFilter()]
  2865. def get_process_list():
  2866. import ctypes
  2867. import collections
  2868. Process = collections.namedtuple('Process', 'pid name exe')
  2869. process_list = []
  2870. if os.name == 'nt':
  2871. PROCESS_QUERY_INFORMATION = 0x0400
  2872. PROCESS_VM_READ = 0x0010
  2873. lpidProcess = (ctypes.c_ulong * 1024)()
  2874. cb = ctypes.sizeof(lpidProcess)
  2875. cbNeeded = ctypes.c_ulong()
  2876. ctypes.windll.psapi.EnumProcesses(ctypes.byref(lpidProcess), cb, ctypes.byref(cbNeeded))
  2877. nReturned = cbNeeded.value/ctypes.sizeof(ctypes.c_ulong())
  2878. pidProcess = [i for i in lpidProcess][:nReturned]
  2879. has_queryimage = hasattr(ctypes.windll.kernel32, 'QueryFullProcessImageNameA')
  2880. for pid in pidProcess:
  2881. hProcess = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid)
  2882. if hProcess:
  2883. modname = ctypes.create_string_buffer(2048)
  2884. count = ctypes.c_ulong(ctypes.sizeof(modname))
  2885. if has_queryimage:
  2886. ctypes.windll.kernel32.QueryFullProcessImageNameA(hProcess, 0, ctypes.byref(modname), ctypes.byref(count))
  2887. else:
  2888. ctypes.windll.psapi.GetModuleFileNameExA(hProcess, 0, ctypes.byref(modname), ctypes.byref(count))
  2889. exe = modname.value
  2890. name = os.path.basename(exe)
  2891. process_list.append(Process(pid=pid, name=name, exe=exe))
  2892. ctypes.windll.kernel32.CloseHandle(hProcess)
  2893. elif sys.platform.startswith('linux'):
  2894. for filename in glob.glob('/proc/[0-9]*/cmdline'):
  2895. pid = int(filename.split('/')[2])
  2896. exe_link = '/proc/%d/exe' % pid
  2897. if os.path.exists(exe_link):
  2898. exe = os.readlink(exe_link)
  2899. name = os.path.basename(exe)
  2900. process_list.append(Process(pid=pid, name=name, exe=exe))
  2901. else:
  2902. try:
  2903. import psutil
  2904. process_list = psutil.get_process_list()
  2905. except StandardError as e:
  2906. logging.exception('psutil.get_process_list() failed: %r', e)
  2907. return process_list
  2908. def pre_start():
  2909. if not OpenSSL:
  2910. logging.warning('python-openssl not found, please install it!')
  2911. if sys.platform == 'cygwin':
  2912. logging.info('cygwin is not officially supported, please continue at your own risk :)')
  2913. #sys.exit(-1)
  2914. elif os.name == 'posix':
  2915. try:
  2916. import resource
  2917. resource.setrlimit(resource.RLIMIT_NOFILE, (8192, -1))
  2918. except ValueError:
  2919. pass
  2920. elif os.name == 'nt':
  2921. import ctypes
  2922. ctypes.windll.kernel32.SetConsoleTitleW(u'GoAgent v%s' % __version__)
  2923. if not common.LISTEN_VISIBLE:
  2924. ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
  2925. else:
  2926. ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
  2927. if common.LOVE_ENABLE and random.randint(1, 100) <= 5:
  2928. title = ctypes.create_unicode_buffer(1024)
  2929. ctypes.windll.kernel32.GetConsoleTitleW(ctypes.byref(title), len(title)-1)
  2930. ctypes.windll.kernel32.SetConsoleTitleW('%s %s' % (title.value, random.choice(common.LOVE_TIP)))
  2931. blacklist = {'360safe': False,
  2932. 'QQProtect': False, }
  2933. softwares = [k for k, v in blacklist.items() if v]
  2934. if softwares:
  2935. tasklist = '\n'.join(x.name for x in get_process_list()).lower()
  2936. softwares = [x for x in softwares if x.lower() in tasklist]
  2937. if softwares:
  2938. title = u'GoAgent 建议'
  2939. error = u'某些安全软件(如 %s)可能和本软件存在冲突,造成 CPU 占用过高。\n如有此现象建议暂时退出此安全软件来继续运行GoAgent' % ','.join(softwares)
  2940. ctypes.windll.user32.MessageBoxW(None, error, title, 0)
  2941. #sys.exit(0)
  2942. if os.path.isfile('/proc/cpuinfo'):
  2943. with open('/proc/cpuinfo', 'rb') as fp:
  2944. m = re.search(r'(?im)(BogoMIPS|cpu MHz)\s+:\s+([\d\.]+)', fp.read())
  2945. if m and float(m.group(2)) < 1000:
  2946. logging.warning("*NOTE*, Please set [gae]window=2 [gae]keepalive=1")
  2947. if GAEProxyHandler.max_window != common.GAE_WINDOW:
  2948. GAEProxyHandler.max_window = common.GAE_WINDOW
  2949. if common.GAE_KEEPALIVE and common.GAE_MODE == 'https':
  2950. GAEProxyHandler.ssl_connection_keepalive = True
  2951. if common.GAE_PAGESPEED and not common.GAE_OBFUSCATE:
  2952. logging.critical("*NOTE*, [gae]pagespeed=1 requires [gae]obfuscate=1")
  2953. sys.exit(-1)
  2954. if common.GAE_SSLVERSION:
  2955. GAEProxyHandler.ssl_version = getattr(ssl, 'PROTOCOL_%s' % common.GAE_SSLVERSION)
  2956. GAEProxyHandler.openssl_context = SSLConnection.context_builder(common.GAE_SSLVERSION)
  2957. if common.GAE_APPIDS[0] == 'goagent':
  2958. logging.critical('please edit %s to add your appid to [gae] !', common.CONFIG_FILENAME)
  2959. sys.exit(-1)
  2960. if common.GAE_MODE == 'http' and common.GAE_PASSWORD == '':
  2961. logging.critical('to enable http mode, you should set %r [gae]password = <your_pass> and [gae]options = rc4', common.CONFIG_FILENAME)
  2962. sys.exit(-1)
  2963. if common.GAE_TRANSPORT:
  2964. GAEProxyHandler.disable_transport_ssl = False
  2965. if common.GAE_REGIONS and not pygeoip:
  2966. logging.critical('to enable [gae]regions mode, you should install pygeoip')
  2967. sys.exit(-1)
  2968. if common.PAC_ENABLE:
  2969. pac_ip = ProxyUtil.get_listen_ip() if common.PAC_IP in ('', '::', '0.0.0.0') else common.PAC_IP
  2970. url = 'http://%s:%d/%s' % (pac_ip, common.PAC_PORT, common.PAC_FILE)
  2971. spawn_later(600, urllib2.build_opener(urllib2.ProxyHandler({})).open, url)
  2972. if not dnslib:
  2973. logging.error('dnslib not found, please put dnslib-0.8.3.egg to %r!', os.path.dirname(os.path.abspath(__file__)))
  2974. sys.exit(-1)
  2975. if not common.DNS_ENABLE:
  2976. if not common.HTTP_DNS:
  2977. common.HTTP_DNS = common.DNS_SERVERS[:]
  2978. for dnsservers_ref in (common.HTTP_DNS, common.DNS_SERVERS):
  2979. any(dnsservers_ref.insert(0, x) for x in [y for y in get_dnsserver_list() if y not in dnsservers_ref])
  2980. AdvancedProxyHandler.dns_servers = common.HTTP_DNS
  2981. AdvancedProxyHandler.dns_blacklist = common.DNS_BLACKLIST
  2982. else:
  2983. AdvancedProxyHandler.dns_servers = common.HTTP_DNS or common.DNS_SERVERS
  2984. AdvancedProxyHandler.dns_blacklist = common.DNS_BLACKLIST
  2985. RangeFetch.threads = common.AUTORANGE_THREADS
  2986. RangeFetch.maxsize = common.AUTORANGE_MAXSIZE
  2987. RangeFetch.bufsize = common.AUTORANGE_BUFSIZE
  2988. RangeFetch.waitsize = common.AUTORANGE_WAITSIZE
  2989. if True:
  2990. GAEProxyHandler.handler_filters.insert(0, AutoRangeFilter())
  2991. if common.GAE_REGIONS:
  2992. GAEProxyHandler.handler_filters.insert(0, DirectRegionFilter(common.GAE_REGIONS))
  2993. if True:
  2994. GAEProxyHandler.handler_filters.insert(0, HostsFilter())
  2995. if True:
  2996. GAEProxyHandler.handler_filters.insert(0, URLRewriteFilter())
  2997. if common.HTTP_FAKEHTTPS:
  2998. GAEProxyHandler.handler_filters.insert(0, FakeHttpsFilter(common.HTTP_FAKEHTTPS, common.HTTP_NOFAKEHTTPS))
  2999. PHPProxyHandler.handler_filters.insert(0, FakeHttpsFilter(common.HTTP_FAKEHTTPS, common.HTTP_NOFAKEHTTPS))
  3000. if common.HTTP_FORCEHTTPS:
  3001. GAEProxyHandler.handler_filters.insert(0, ForceHttpsFilter(common.HTTP_FORCEHTTPS, common.HTTP_NOFORCEHTTPS))
  3002. PHPProxyHandler.handler_filters.insert(0, ForceHttpsFilter(common.HTTP_FORCEHTTPS, common.HTTP_NOFORCEHTTPS))
  3003. if common.HTTP_WITHGAE:
  3004. GAEProxyHandler.handler_filters.insert(0, WithGAEFilter(common.HTTP_WITHGAE))
  3005. if common.USERAGENT_ENABLE:
  3006. GAEProxyHandler.handler_filters.insert(0, UserAgentFilter(common.USERAGENT_STRING))
  3007. PHPProxyHandler.handler_filters.insert(0, UserAgentFilter(common.USERAGENT_STRING))
  3008. if common.LISTEN_USERNAME:
  3009. GAEProxyHandler.handler_filters.insert(0, AuthFilter(common.LISTEN_USERNAME, common.LISTEN_PASSWORD))
  3010. def main():
  3011. global __file__
  3012. __file__ = os.path.abspath(__file__)
  3013. if os.path.islink(__file__):
  3014. __file__ = getattr(os, 'readlink', lambda x: x)(__file__)
  3015. os.chdir(os.path.dirname(os.path.abspath(__file__)))
  3016. logging.basicConfig(level=logging.DEBUG if common.LISTEN_DEBUGINFO else logging.INFO, format='%(levelname)s - %(asctime)s %(message)s', datefmt='[%b %d %H:%M:%S]')
  3017. pre_start()
  3018. CertUtil.check_ca()
  3019. sys.stderr.write(common.info())
  3020. uvent_enabled = 'uvent.loop' in sys.modules and isinstance(gevent.get_hub().loop, __import__('uvent').loop.UVLoop)
  3021. if common.PHP_ENABLE:
  3022. host, port = common.PHP_LISTEN.split(':')
  3023. HandlerClass = ((PHPProxyHandler, GreenForwardPHPProxyHandler) if not common.PROXY_ENABLE else (ProxyChainPHPProxyHandler, ProxyChainGreenForwardPHPProxyHandler))[uvent_enabled]
  3024. server = LocalProxyServer((host, int(port)), HandlerClass)
  3025. thread.start_new_thread(server.serve_forever, tuple())
  3026. if common.PAC_ENABLE:
  3027. server = LocalProxyServer((common.PAC_IP, common.PAC_PORT), PACProxyHandler)
  3028. thread.start_new_thread(server.serve_forever, tuple())
  3029. if common.DNS_ENABLE:
  3030. try:
  3031. sys.path += ['.']
  3032. from dnsproxy import DNSServer
  3033. host, port = common.DNS_LISTEN.split(':')
  3034. server = DNSServer((host, int(port)), dns_servers=common.DNS_SERVERS, dns_blacklist=common.DNS_BLACKLIST, dns_tcpover=common.DNS_TCPOVER)
  3035. thread.start_new_thread(server.serve_forever, tuple())
  3036. except ImportError:
  3037. logging.exception('GoAgent DNSServer requires dnslib and gevent 1.0')
  3038. sys.exit(-1)
  3039. HandlerClass = ((GAEProxyHandler, GreenForwardGAEProxyHandler) if not common.PROXY_ENABLE else (ProxyChainGAEProxyHandler, ProxyChainGreenForwardGAEProxyHandler))[uvent_enabled]
  3040. server = LocalProxyServer((common.LISTEN_IP, common.LISTEN_PORT), HandlerClass)
  3041. try:
  3042. server.serve_forever()
  3043. except SystemError as e:
  3044. if '(libev) select: ' in repr(e):
  3045. logging.error('PLEASE START GOAGENT BY uvent.bat')
  3046. sys.exit(-1)
  3047. if __name__ == '__main__':
  3048. main()