def parseRelayHeader(self, dat): """Extract addr, port and rest data from relay request. Parts except CMD (first byte) and HMAC (not encrypted) will be decrypted if encryption enabled. CMD should be raw but checked before calling this function.""" digest = dat[-DIGEST_LEN:] err = '' if len(digest) != DIGEST_LEN: err = 'incorrect digest length' if not hmac.compare_digest(digest, _get_digest(dat[:-DIGEST_LEN])): err = 'authentication failed' dat = self.decrypt(dat[1:-DIGEST_LEN]) if err: raise ValueError(err + ', decrypted dat: %s' % dat[:self.DAT_LOG_MAX_LEN]) addr, port, remainIdx = parse_socks_addr(dat[TIMESTAMP_LEN:], allow_remain=True) remain = dat[TIMESTAMP_LEN + remainIdx:] # remainIdx is relative to addrRest # If we are using SSL then checking timestamp is meaningless. # But for simplicity this field still present. stamp = None if not config.tun_ssl: try: stamp = struct.unpack('>d', dat[:TIMESTAMP_LEN])[0] except struct.error: raise ValueError('invalid timestamp') if abs(time.time() - stamp) > self.REQ_TTL: raise ValueError( 'request expired (%.1fs old), decrypted dat: %s' % (time.time() - stamp, dat[:self.DAT_LOG_MAX_LEN])) return addr, port, remain, stamp
def socks5_tcp_handler(dat, reader, writer): # handle auth method selection if len(dat) < 2 or len(dat) != dat[1] + 2: logging.warning('bad SOCKS v5 request') return writer.close() writer.write(b'\x05\x00') # \x00 == NO AUTHENTICATION REQUIRED # handle relay request dat = yield from reader.read(262) try: cmd, addr_header = dat[1], dat[2:] target_addr, target_port = parse_socks_addr(addr_header) except (ValueError, IndexError): logging.warning('invalid SOCKS v5 relay request') return writer.close() logging.info('requesting %s:%d' % (target_addr, target_port)) if cmd != 0x01: # CONNECT writer.write(b'\x05\x07\x00\x01' + b'\x00' * 6) # \x07 == COMMAND NOT SUPPORTED return writer.close() # By accepting request before connected to target delay can be lowered (of a round-trip). # But SOCKS client can't get real reason when error happens (Firefox always display # connection reset error). Dirty solution: generate a HTML page when a HTTP request failed writer.write(b'\x05\x00\x00\x01' + b'\x01' * 6) # \x00 == SUCCEEDED dat = yield from reader.read(2048) if not dat: return writer.close() yield from WSTunClientProtocol.startProxy(addr_header, dat, reader, writer)
def parseRelayHeader(self, dat): """Extract addr, port and rest data from relay request. Parts except CMD (first byte) and HMAC (not encrypted) will be decrypted if encryption enabled. CMD should be raw but checked before calling this function.""" digest = dat[-DIGEST_LEN:] err = '' if len(digest) != DIGEST_LEN: err = 'incorrect digest length' if not hmac.compare_digest(digest, _get_digest(dat[:-DIGEST_LEN])): err = 'authentication failed' dat = self.decrypt(dat[1:-DIGEST_LEN]) if err: raise ValueError(err + ', decrypted dat: %s' % dat[:self.DAT_LOG_MAX_LEN]) addr, port, remainIdx = parse_socks_addr(dat[TIMESTAMP_LEN:], allow_remain=True) remain = dat[TIMESTAMP_LEN+remainIdx:] # remainIdx is relative to addrRest # If we are using SSL then checking timestamp is meaningless. # But for simplicity this field still present. stamp = None if not config.tun_ssl: try: stamp = struct.unpack('>d', dat[:TIMESTAMP_LEN])[0] except struct.error: raise ValueError('invalid timestamp') if abs(time.time() - stamp) > self.REQ_TTL: raise ValueError('request expired (%.1fs old), decrypted dat: %s' % (time.time() - stamp, dat[:self.DAT_LOG_MAX_LEN])) return addr, port, remain, stamp
def socks5_tcp_handler(dat, reader, writer): # handle auth method selection if len(dat) < 2 or len(dat) != dat[1] + 2: logging.warning('bad SOCKS v5 request') return writer.close() writer.write(b'\x05\x00') # \x00 == NO AUTHENTICATION REQUIRED # handle relay request dat = yield from reader.read(262) try: cmd, addr_header = dat[1], dat[2:] target_addr, target_port = parse_socks_addr(addr_header) except (ValueError, IndexError): logging.warning('invalid SOCKS v5 relay request') return writer.close() if cmd != 0x01: # CONNECT writer.write(b'\x05\x07\x00\x01' + b'\x00' * 6) # \x07 == COMMAND NOT SUPPORTED return writer.close() # Delay can be lowered (of a round-trip) by accepting request before connected to target. # But SOCKS client can't get real reason when error happens (not a big problem, Firefox always # display connection reset error). Dirty solution: generate a HTML page when a HTTP request failed writer.write(b'\x05\x00\x00\x01' + b'\x01' * 6) # \x00 == SUCCEEDED try: dat = yield from wait_for(reader.read(2048), 0.02) if not dat: return writer.close() except asyncio.TimeoutError: # 20ms passed and no data received, rare but legal behavior. # timeout may always happen if set to 10ms, and enable asyncio library debug mode will "fix" it # e.g. Old SSH client will wait for server after conn established dat = None async_( WSTunClientProtocol.openTunnel((target_addr, target_port), dat, reader, writer))