def onMessage(self, dat, isBinary): if not isBinary: logging.error('non binary ws message received') return self.sendClose(3000) cmd = ord(self.decrypt(dat[:1])) if cmd == self.CMD_RST: reason, err = self.parseResetMessage(dat) if reason: err = translate_err_msg(err) logging.info('%s: %s' % (reason, err)) if self.canReturnErrorPage: self._writer.write(gen_error_page(reason, err)) self.onResetTunnel() elif cmd == self.CMD_DAT: dat = self.decrypt(dat[1:]) if self.tunState != self.TUN_STATE_USING: if self.tunState == self.TUN_STATE_IDLE: logging.debug('IDLE should not appear here!') # Reset command sent, but server will keep sending data before # receiving the command. # Can't just throw away dat, because decryptor need to be updated return self.canReturnErrorPage = False self._writer.write(dat) else: logging.error('wrong command')
def onMessage(self, dat, isBinary): if not isBinary: logging.error('non binary ws message received') return self.sendClose(3000) cmd = ord(self.decrypt(dat[:1])) if cmd == self.CMD_RST: msg = self.parseResetMessage(dat) if not msg.startswith(' '): logging.info('tunnel abnormal reset: %s' % msg) if self.canReturnErrorPage: title, __, reason = msg.partition(':') self._writer.write(gen_error_page(title, translate_err_msg(reason.strip()))) self.onResetTunnel() elif cmd == self.CMD_DAT: dat = self.decrypt(dat[1:]) if self.tunState != self.TUN_STATE_USING: if self.tunState == self.TUN_STATE_IDLE: logging.debug('IDLE should not appear here!') # Reset command sent, but server will keep sending data before # receiving the command. # Can't just throw away dat, because decryptor need to be updated return self.canReturnErrorPage = False self._writer.write(dat) else: logging.error('wrong command')
def onMessage(self, dat, isBinary): if not isBinary: logging.error('non binary ws message received') return self.sendClose(3000) cmd = ord(self.decrypt(dat[:1])) if cmd == self.CMD_RST: msg = self.parseResetMessage(dat) if not msg.startswith(' '): logging.info('tunnel abnormal reset: %s' % msg) if self.canReturnErrorPage: title, __, reason = msg.partition(':') self._writer.write(gen_error_page(title, translate_err_msg(reason.strip()))) self.onResetTunnel() elif cmd == self.CMD_DAT: dat = self.decrypt(dat[1:]) if self.tunState != self.TUN_STATE_USING: # Reset command sent, but server will keep sending data before # receiving the command. # Can't just throw away dat, because decryptor need to be updated return self.canReturnErrorPage = False self._writer.write(dat) else: logging.error('wrong command')
def onClose(self, *args): if not self.tunOpen.done(): # sometimes reason can be None in extremely poor network msg = self.wasNotCleanReason or '' msg = translate_err_msg(msg) logging.error("can't connect to server: %s" % msg) if self.wasNotCleanReason and self.canReturnErrorPage: # write before closing writer self._writer.write(gen_error_page("can't connect to wstan server", msg)) RelayMixin.onClose(self, *args, logWarn=False) self.tunOpen.cancel() else: RelayMixin.onClose(self, *args) self.tryRemoveFromPool()
def startProxy(cls, addrHeader, dat, reader, writer): canErr = can_return_error_page(dat) if cls.pool: logging.debug('reuse tunnel from pool (total %s)' % len(cls.pool)) tun = cls.pool[0] tun.checkTimeoutTask.cancel() tun.checkTimeoutTask = None tun.tryRemoveFromPool() tun.setAutoPing(cls.TUN_AUTO_PING_INTERVAL, cls.TUN_AUTO_PING_TIMEOUT) tun.sendMessage(tun.makeRelayHeader(addrHeader, dat), True) else: try: sock = None if config.proxy: sock = yield from setup_http_tunnel() tun = (yield from loop.create_connection( factory, None if sock else config.uri_addr, None if sock else config.uri_port, server_hostname=config.uri_addr if config.tun_ssl else None, sock=sock, ssl=config.tun_ssl))[1] # lower latency by sending relay header and data in ws handshake tun.customUriPath = '/' + base64.urlsafe_b64encode( tun.makeRelayHeader(addrHeader, dat)).decode() async_(tun.restartHandshake()) yield from wait_for(tun.tunOpen, tun.openHandshakeTimeout) except Exception as e: if isinstance(e, (asyncio.TimeoutError, asyncio.CancelledError)): # sometimes reason can be None in extremely poor network msg = tun.wasNotCleanReason or '' else: msg = str(e) msg = translate_err_msg(msg) logging.error("can't connect to server: %s" % msg) if canErr: writer.write( gen_error_page("can't connect to wstan server", msg)) return writer.close() tun.canReturnErrorPage = canErr tun.setProxy(reader, writer)
def startProxy(cls, addrHeader, dat, reader, writer): if not dat: logging.debug('startProxy with no data') canErr = can_return_error_page(dat) if cls.pool: logging.debug('reuse tunnel from pool (total %s)' % len(cls.pool)) tun = cls.pool[0] tun.checkTimeoutTask.cancel() tun.checkTimeoutTask = None tun.tryRemoveFromPool() tun.setAutoPing(cls.TUN_AUTO_PING_INTERVAL, cls.TUN_AUTO_PING_TIMEOUT) tun.canReturnErrorPage = canErr tun.setProxy(reader, writer) tun.sendMessage(tun.makeRelayHeader(addrHeader, dat), True) else: try: sock = None if config.proxy: sock = yield from setup_http_tunnel() tun = (yield from loop.create_connection( factory, None if sock else config.uri_addr, None if sock else config.uri_port, server_hostname=config.uri_addr if config.tun_ssl else None, sock=sock, ssl=config.tun_ssl))[1] # Lower latency by sending relay header and data in ws handshake tun.customUriPath = factory.path + base64.urlsafe_b64encode( tun.makeRelayHeader(addrHeader, dat)).decode() tun.canReturnErrorPage = canErr tun.setProxy(reader, writer, startPushLoop=False) async_(tun.restartHandshake()) # Data may arrive before setProxy if wait for onOpen here # and then set proxy. except Exception as e: msg = translate_err_msg(str(e)) logging.error("can't connect to server: %s" % msg) if canErr: writer.write(gen_error_page("can't connect to wstan server", msg)) return writer.close()
def startProxy(cls, addrHeader, dat, reader, writer): canErr = can_return_error_page(dat) if cls.pool: logging.debug('reuse tunnel from pool (total %s)' % len(cls.pool)) tun = cls.pool.popleft() tun.checkTimeoutTask.cancel() tun.checkTimeoutTask = None tun.inPool = False tun.disableAutoPing() tun.sendMessage(tun.makeRelayHeader(addrHeader, dat), True) else: try: sock = None if config.proxy: sock = yield from setup_http_tunnel() tun = (yield from loop.create_connection( factory, None if sock else config.uri_addr, None if sock else config.uri_port, server_hostname=config.uri_addr if config.tun_ssl else None, sock=sock, ssl=config.tun_ssl))[1] # lower latency by sending relay header and data in ws handshake tun.customUriPath = '/' + base64.urlsafe_b64encode(tun.makeRelayHeader(addrHeader, dat)).decode() asyncio.async(tun.restartHandshake()) yield from asyncio.wait_for(tun.tunOpen, tun.openHandshakeTimeout) except Exception as e: if isinstance(e, (asyncio.TimeoutError, asyncio.CancelledError)): # sometimes reason can be None in extremely poor network msg = tun.wasNotCleanReason or '' else: msg = str(e) msg = translate_err_msg(msg) logging.error("can't connect to server: %s" % msg) if canErr: writer.write(gen_error_page("can't connect to wstan server", msg)) return writer.close() tun.canReturnErrorPage = canErr tun.setProxy(reader, writer)
def onMessage(self, dat, isBinary): if not isBinary: logging.error('non binary ws message received') return self.sendClose(3000) cmd = ord(self.decrypt(dat[:1])) if cmd == self.CMD_RST: msg = self.parseResetMessage(dat) if not msg.startswith(' '): logging.info('tunnel abnormal reset: %s' % msg) if self.canReturnErrorPage: title, __, reason = msg.partition(':') self._writer.write(gen_error_page(title, translate_err_msg(reason.strip()))) self.onResetTunnel() elif cmd == self.CMD_DAT: dat = self.decrypt(dat[1:]) if self.tunState != self.TUN_STATE_USING: # why this happens? return self.canReturnErrorPage = False self._writer.write(dat) else: logging.error('wrong command')
def openTunnel(cls, target, dat, reader, writer, retryCount=0): logging.info('requesting %s:%d' % target) if not dat: logging.debug('openTunnel with no data') canErr = can_return_error_page(dat) if cls.pool: # reuse from pool logging.debug('reuse tunnel from pool (total %s)' % len(cls.pool)) tun = cls.pool[0] tun.checkTimeoutTask.cancel() tun.checkTimeoutTask = None tun.tryRemoveFromPool() tun.setAutoPing(cls.TUN_AUTO_PING_INTERVAL, cls.TUN_AUTO_PING_TIMEOUT) tun.canReturnErrorPage = canErr tun.setProxy(reader, writer) tun.sendMessage(tun.makeRelayHeader(target, dat), True) return # new tunnel try: if retryCount >= cls.MAX_RETRY_COUNT: raise ConnectionResetError( 'run into tcp reset, all retries failed') sock = None if config.proxy: sock = yield from setup_http_tunnel() tun = factory() # Lower latency by sending relay header and data in ws handshake tun.customUriPath = factory.path + base64.urlsafe_b64encode( tun.makeRelayHeader(target, dat)).decode() tun.canReturnErrorPage = canErr # Data may arrive before setProxy if wait for tunOpen here and then set proxy. tun.setProxy(reader, writer, startPushLoop=False) # push loop will start in onOpen if config.tfo: assert not config.proxy and not config.tun_ssl tun.noSendHandshake = True tun.startHandshake() # tfo is meaningless if handshake data can't fit into TCP SYN packet # switch back to normal sock_connect just in case my Windows tfo extension has bug tfoDat = tun.http_request_data if len( tun.http_request_data) <= 1400 else None sock = yield from my_sock_connect(config.uri_addr, config.uri_port, tfo_dat=tfoDat) # it will return after SYN,ACK received regardless of TFO if not tfoDat: loop.sock_sendall(sock, tun.http_request_data) yield from loop.create_connection( lambda: tun, None if sock else config.uri_addr, None if sock else config.uri_port, server_hostname=config.uri_addr if config.tun_ssl else None, sock=sock, ssl=config.tun_ssl) except Exception as e: msg = translate_err_msg(str(e)) dest = 'proxy' if config.proxy and not sock else 'wstan server' logging.error("can't connect to %s: %s" % (dest, msg)) if canErr: writer.write(gen_error_page("can't connect to " + dest, msg)) return writer.close() try: yield from wait_for(tun.tunOpen, None) except CancelledError: # sometimes reason can be None in extremely poor network msg = tun.wasNotCleanReason or '' if isinstance(tun.connLostReason, ConnectionResetError): # GFW random reset HTTP stream it can't recognize, just retry return async_( cls.openTunnel(target, dat, reader, writer, retryCount + 1)) msg = translate_err_msg(msg) logging.error("can't connect to server: %s" % msg) if tun.wasNotCleanReason and tun.canReturnErrorPage: # write before closing writer writer.write( gen_error_page("can't connect to wstan server", msg)) return writer.close() if retryCount > 0: logging.debug('tcp reset happen, retried %d times' % retryCount)