def decrypt_parse(self, data): cipher = Encryptor(self.__key, self.method) data = cipher.decrypt(data) data = io.BytesIO(data) addrtype = data.read(1)[0] if addrtype == 1: addr = data.read(4) addr = socket.inet_ntoa(addr) elif addrtype == 3: addr = data.read(1) addr = data.read(addr[0]) addr = addr.decode('ascii') else: addr = data.read(16) addr = socket.inet_ntop(socket.AF_INET6, addr) port = data.read(2) port, = struct.unpack('>H', port) dgram = data.read() return (addr, port), dgram, data
class Hxs2Connection: bufsize = 65535 - 22 def __init__(self, proxy, manager): if not isinstance(proxy, ParentProxy): proxy = ParentProxy(proxy, proxy) self.logger = logging.getLogger('hxs2') self.proxy = proxy self.name = self.proxy.name self.timeout = 6 # read frame_data timeout self._manager = manager self._ping_test = False self._ping_time = 0 self.connected = False self.connection_lost = False self.udp_relay_support = None self.udp_event = None self._psk = self.proxy.query.get('PSK', [''])[0] self.method = self.proxy.query.get('method', [DEFAULT_METHOD])[0].lower() self.mode = int(self.proxy.query.get('mode', ['0'])[0]) self.hash_algo = self.proxy.query.get('hash', [DEFAULT_HASH])[0].upper() self.remote_reader = None self.remote_writer = None self._socport = None self.__pskcipher = None self.__cipher = None self._next_stream_id = 1 self._settings_async_drain = None self._client_writer = {} self._client_status = {} self._client_resume_reading = {} self._client_drain_lock = {} self._stream_status = {} self._stream_addr = {} self._stream_task = {} self._last_active = {} self._last_active_c = time.monotonic() self._last_ping_log = 0 self._connection_task = None self._connection_stat = None self._last_direction = SEND self._last_count = 0 self._buffer_size_ewma = 0 self._recv_tp_max = 0 self._recv_tp_ewma = 0 self._sent_tp_max = 0 self._sent_tp_ewma = 0 self._stat_data_recv = 0 self._stat_total_recv = 1 self._stat_recv_tp = 0 self._stat_data_sent = 0 self._stat_total_sent = 1 self._stat_sent_tp = 0 self._lock = Lock() async def connect(self, addr, port, timeout=3): self.logger.debug('hxsocks2 send connect request') if self.connection_lost: self._manager.remove(self) raise ConnectionLostError(0, 'hxs2 connection lost') if not self.connected: self._manager.remove(self) raise ConnectionResetError(0, 'hxs2 not connected.') # send connect request payload = b''.join([ chr(len(addr)).encode('latin1'), addr.encode(), struct.pack('>H', port), b'\x00' * random.randint(64, 255), ]) stream_id = self._next_stream_id self._next_stream_id += 1 if self._next_stream_id > MAX_STREAM_ID: self.logger.error('MAX_STREAM_ID reached') self._manager.remove(self) self.send_frame(HEADERS, OPEN, stream_id, payload) self._stream_addr[stream_id] = (addr, port) # wait for server response event = Event() self._client_status[stream_id] = event self._client_resume_reading[stream_id] = asyncio.Event() self._client_resume_reading[stream_id].set() # await event.wait() fut = event.wait() try: await asyncio.wait_for(fut, timeout=timeout) except asyncio.TimeoutError: self.logger.error('no response from %s, timeout=%.3f', self.name, timeout) del self._client_status[stream_id] self.print_status() self.send_ping() raise del self._client_status[stream_id] if self._stream_status[stream_id] == OPEN: socketpair_a, socketpair_b = socket.socketpair() if sys.platform == 'win32': socketpair_a.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) socketpair_b.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) reader, writer = await asyncio.open_connection(sock=socketpair_b, limit=131072) writer.transport.set_write_buffer_limits(CLIENT_WRITE_BUFFER) self._client_writer[stream_id] = writer self._last_active[stream_id] = time.monotonic() self._client_drain_lock[stream_id] = asyncio.Lock() # start forwarding self._stream_task[stream_id] = asyncio.ensure_future( self.read_from_client(stream_id, reader)) return socketpair_a raise ConnectionResetError( 0, 'remote connect to %s:%d failed.' % (addr, port)) async def read_from_client(self, stream_id, client_reader): self.logger.debug('start read from client') while not self.connection_lost: await self._client_resume_reading[stream_id].wait() fut = client_reader.read(self.bufsize) try: data = await asyncio.wait_for(fut, timeout=6) self._last_active[stream_id] = time.monotonic() except asyncio.TimeoutError: if time.monotonic() - self._last_active[stream_id] < STREAM_TIMEOUT and\ self._stream_status[stream_id] == OPEN: continue data = b'' except ConnectionError: await self.close_stream(stream_id) return if not data: # close stream(LOCAL) self._stream_status[stream_id] |= EOF_SENT self.send_frame(HEADERS, END_STREAM_FLAG, stream_id, bytes(random.randint(8, 256))) break if self._stream_status[stream_id] & EOF_SENT: self.logger.error( 'data recv from client, while stream is closed!') await self.close_stream(stream_id) return await self.send_data_frame(stream_id, data) while time.monotonic() - self._last_active[stream_id] < 12: await asyncio.sleep(6) await self.close_stream(stream_id) def send_frame(self, type_, flags, stream_id, payload): self.logger.debug('send_frame type: %d, stream_id: %d', type_, stream_id) if self.connection_lost: self.logger.error('send_frame: connection closed. %s', self.name) return if type_ != PING: self._last_active_c = time.monotonic() elif flags == 0: self._ping_time = time.monotonic() if self._last_direction == RECV: self._last_direction = SEND self._last_count = 0 header = struct.pack('>BBH', type_, flags, stream_id) data = header + payload ct = self.__cipher.encrypt(data) self.remote_writer.write(struct.pack('>H', len(ct)) + ct) self._stat_total_sent += len(ct) + 2 self._stat_sent_tp += len(ct) + 2 self._last_count += 1 if self._settings_async_drain is None and random.random() < 0.1: self._settings_async_drain = False self.send_frame(SETTINGS, 0, 1, bytes(random.randint(64, 256))) if type_ == DATA and self._last_count > 10 and random.random() < 0.01: self.send_ping(False) def send_ping(self, test=True): if self._ping_time == 0: self._ping_test = test self.send_frame(PING, 0, 0, bytes(random.randint(64, 256))) def send_one_data_frame(self, stream_id, data): payload = struct.pack('>H', len(data)) + data diff = self.bufsize - len(data) payload += bytes(random.randint(min(diff, 8), min(diff, 255))) self.send_frame(DATA, 0, stream_id, payload) async def send_data_frame(self, stream_id, data): data_len = len(data) if data_len > 16386 and random.random() < 0.1: data = io.BytesIO(data) data_ = data.read(random.randint(256, 16386 - 22)) while data_: self.send_one_data_frame(stream_id, data_) if random.random() < 0.1: self.send_frame(PING, 0, 0, bytes(random.randint(256, 1024))) data_ = data.read(random.randint(256, 8192 - 22)) await asyncio.sleep(0) else: self.send_one_data_frame(stream_id, data) self._stat_data_sent += data_len buffer_size = self.remote_writer.transport.get_write_buffer_size() self._buffer_size_ewma = self._buffer_size_ewma * 0.87 + buffer_size * 0.13 await self.drain() async def drain(self): async with self._lock: if self.connection_lost: return try: await self.remote_writer.drain() except ConnectionError: self.connection_lost = True async def udp_associate(self, udp_server): if self.connection_lost: self._manager.remove(self) raise ConnectionLostError(0, 'hxs2 connection lost') if not self.connected: self._manager.remove(self) raise ConnectionResetError(0, 'hxs2 not connected.') if self.udp_relay_support is None: # ask server for udp_support self.send_frame(UDP_ASSOCIATE, OPEN, 0, bytes(random.randint(64, 256))) if not self.udp_event: self.udp_event = Event() fut = self.udp_event.wait() try: await asyncio.wait_for(fut, timeout=self.timeout) except asyncio.TimeoutError: if not self.udp_relay_support: self.udp_relay_support = False self.logger.error( '%s does not seem to support UDP_ASSOCIATE, timeout: %d', self.name, self.timeout) if not self.udp_relay_support: raise ConnectionResetError( 0, '%s does not seem to support UDP_ASSOCIATE' % self.name) # send udp_assicoate request stream_id = self._next_stream_id self._next_stream_id += 1 if self._next_stream_id > MAX_STREAM_ID: self.logger.error('MAX_STREAM_ID reached') self._manager.remove(self) self.send_frame(UDP_ASSOCIATE, OPEN, stream_id, bytes(random.randint(64, 256))) self._stream_status[stream_id] = OPEN self._client_resume_reading[stream_id] = asyncio.Event() self._client_resume_reading[stream_id].set() relay = UDPRelayHxs2(udp_server, stream_id, self) self._client_writer[stream_id] = relay return relay async def read_from_connection(self): self.logger.debug('start read from connection') while not self.connection_lost: try: # read frame_len intv = 2 if self._ping_test else 6 try: frame_len = await self._rfile_read(2, timeout=intv) frame_len, = struct.unpack('>H', frame_len) except asyncio.TimeoutError: if self._ping_test and time.monotonic( ) - self._ping_time > 6: self.logger.warning('server no response %s', self.proxy.name) break if time.monotonic() - self._last_active_c > CONN_TIMEOUT: # no point keeping so long break if time.monotonic() - self._last_active_c > 10: if not self._ping_test: self.send_ping() continue except (ConnectionError, asyncio.IncompleteReadError) as err: # destroy connection self.logger.error('read from connection error: %r', err) break # read frame_data try: frame_data = await self._rfile_read(frame_len, timeout=self.timeout) frame_data = self.__cipher.decrypt(frame_data) self._stat_total_recv += frame_len + 2 self._stat_recv_tp += frame_len + 2 except (ConnectionError, asyncio.TimeoutError, asyncio.IncompleteReadError, InvalidTag) as err: # destroy connection self.logger.error('read frame data error: %r, timeout %s', err, self.timeout) break if self._last_direction == SEND: self._last_direction = RECV self._last_count = 0 self._last_count += 1 if self._last_count > 10 and random.random() < 0.1: self.send_frame(PING, PONG, 0, bytes(random.randint(64, 256))) # parse chunk_data # +------+-------------------+----------+ # | type | flags | stream_id | payload | # +------+-------------------+----------+ # | 1 | 1 | 2 | Variable | # +------+-------------------+----------+ header, payload = frame_data[:4], frame_data[4:] frame_type, frame_flags, stream_id = struct.unpack( '>BBH', header) payload = io.BytesIO(payload) self.logger.debug( 'recv frame_type: %s, stream_id: %s, size: %s', frame_type, stream_id, frame_len) if frame_type == DATA: # 0 self._last_active_c = time.monotonic() if self._stream_status[stream_id] & EOF_RECV: # from server send buffer self.logger.debug( 'DATA recv Stream CLOSED, status: %s', self._stream_status[stream_id]) continue # first 2 bytes of payload indicates data_len, the rest would be padding data_len, = struct.unpack('>H', payload.read(2)) data = payload.read(data_len) if len(data) != data_len: # something went wrong, destory connection self.logger.error('len(data) != data_len') break # sent data to stream try: self._last_active[stream_id] = time.monotonic() if isinstance(self._client_writer[stream_id], UDPRelayHxs2): await self._client_writer[stream_id ].on_remote_recv(data) else: self._client_writer[stream_id].write(data) await self.client_writer_drain(stream_id) self._stat_data_recv += data_len except OSError: # client error, reset stream asyncio.ensure_future(self.close_stream(stream_id)) elif frame_type == HEADERS: # 1 self._last_active_c = time.monotonic() if self._next_stream_id == stream_id: # server is not supposed to open a new stream # send connection error? break if stream_id < self._next_stream_id: if frame_flags == END_STREAM_FLAG: self._stream_status[stream_id] |= EOF_RECV if stream_id in self._client_writer: try: self._client_writer[stream_id].write_eof() except OSError: self._stream_status[stream_id] = CLOSED if self._stream_status[stream_id] == CLOSED: asyncio.ensure_future( self.close_stream(stream_id)) else: # confirm a stream is opened if stream_id in self._client_status: self._stream_status[stream_id] = OPEN self._client_status[stream_id].set() else: addr = '%s:%s' % self._stream_addr[stream_id] self.logger.info( '%s stream open, client closed, %s', self.name, addr) self._stream_status[stream_id] = CLOSED self.send_frame(RST_STREAM, 0, stream_id, bytes(random.randint(8, 256))) elif frame_type == RST_STREAM: # 3 self._last_active_c = time.monotonic() self._stream_status[stream_id] = CLOSED if stream_id in self._client_status: self._client_status[stream_id].set() asyncio.ensure_future(self.close_stream(stream_id)) elif frame_type == SETTINGS: if stream_id == 1: self._settings_async_drain = True elif frame_type == PING: # 6 if frame_flags == PONG: resp_time = time.monotonic() - self._ping_time if time.monotonic() - self._last_ping_log > 30: self.logger.info('server response time: %.3f %s', resp_time, self.proxy.name) self._last_ping_log = time.monotonic() if resp_time < 0.5: self.proxy.log('', resp_time) self._ping_test = False self._ping_time = 0 else: self.send_frame(PING, PONG, 0, bytes(random.randint(64, 2048))) elif frame_type == GOAWAY: # 7 # no more new stream max_stream_id = payload.read(2) self._manager.remove(self) for stream_id, client_writer in self._client_writer: if stream_id > max_stream_id: # reset stream client_writer.close() try: await client_writer.wait_closed() except ConnectionError: pass elif frame_type == WINDOW_UPDATE: # 8 if frame_flags == 1: self._client_resume_reading[stream_id].clear() else: self._client_resume_reading[stream_id].set() elif frame_type == UDP_ASSOCIATE: # 20 if stream_id == 0: self.udp_relay_support = True if self.udp_event: self.udp_event.set() except Exception as err: self.logger.error('CONNECTION BOOM! %r', err) self.logger.error(traceback.format_exc()) break # out of loop, destroy connection self.connection_lost = True self._manager.remove(self) self.logger.warning('out of loop %s', self.proxy.name) self.logger.info('total_recv: %d, data_recv: %d %.3f', self._stat_total_recv, self._stat_data_recv, self._stat_data_recv / self._stat_total_recv) self.logger.info('total_sent: %d, data_sent: %d %.3f', self._stat_total_sent, self._stat_data_sent, self._stat_data_sent / self._stat_total_sent) self.print_status() for sid, status in self._client_status.items(): if isinstance(status, Event): self._stream_status[sid] = CLOSED status.set() task_list = [] for stream_id in self._client_writer: self._stream_status[stream_id] = CLOSED if not self._client_writer[stream_id].is_closing(): self._client_writer[stream_id].close() task_list.append(self._client_writer[stream_id]) self._client_writer = {} task_list = [asyncio.create_task(w.wait_closed()) for w in task_list] task_list.append(asyncio.create_task(self.close())) if task_list: await asyncio.wait(task_list) async def get_key(self, timeout, tcp_nodelay): self.logger.debug('hxsocks2 getKey') usn, psw = (self.proxy.username, self.proxy.password) self.logger.info('%s connect to server', self.name) from .connection import open_connection self.remote_reader, self.remote_writer, _ = await open_connection( self.proxy.hostname, self.proxy.port, proxy=self.proxy.get_via(), timeout=timeout, tunnel=True, limit=262144, tcp_nodelay=tcp_nodelay) self.remote_writer.transport.set_write_buffer_limits(262144) # prep key exchange request self.__pskcipher = Encryptor(self._psk, self.method) ecc = ECC(self.__pskcipher.key_len) pubk = ecc.get_pub_key() timestamp = struct.pack('>I', int(time.time()) // 30) data = b''.join([ chr(len(pubk)).encode('latin1'), pubk, hmac.new(psw.encode(), timestamp + pubk + usn.encode(), hashlib.sha256).digest(), bytes((self.mode, )), bytes(random.randint(64, 450)) ]) data = chr(20).encode() + struct.pack('>H', len(data)) + data ct = self.__pskcipher.encrypt(data) # send key exchange request self.remote_writer.write(ct) await self.remote_writer.drain() # read iv iv_ = await self._rfile_read(self.__pskcipher.iv_len, timeout) self.__pskcipher.decrypt(iv_) # read server response if is_aead(self.method): ct_len = await self._rfile_read(18, timeout) ct_len = self.__pskcipher.decrypt(ct_len) ct_len, = struct.unpack('!H', ct_len) ct_ = await self._rfile_read(ct_len + 16) ct_ = self.__pskcipher.decrypt(ct_) data = ct_[2:] else: resp_len = await self._rfile_read(2, timeout) resp_len = self.__pskcipher.decrypt(resp_len) resp_len, = struct.unpack('>H', resp_len) data = await self._rfile_read(resp_len) data = self.__pskcipher.decrypt(data) data = io.BytesIO(data) resp_code = byte2int(data.read(1)) if resp_code == 0: self.logger.debug('hxsocks read key exchange respond') pklen = byte2int(data.read(1)) scertlen = byte2int(data.read(1)) siglen = byte2int(data.read(1)) server_key = data.read(pklen) auth = data.read(32) server_cert = data.read(scertlen) signature = data.read(siglen) mode = data.read(1)[0] # TODO: ask user if a certificate should be accepted or not. host, port = self.proxy._host_port server_id = '%s_%d' % (host, port) if server_id not in KNOWN_HOSTS: self.logger.info('hxs: server %s new cert %s saved.', server_id, hashlib.sha256(server_cert).hexdigest()[:8]) with open('./.hxs_known_hosts/' + server_id + '.cert', 'wb') as f: f.write(server_cert) KNOWN_HOSTS[server_id] = server_cert elif KNOWN_HOSTS[server_id] != server_cert: self.logger.error( 'hxs: server %s certificate mismatch! PLEASE CHECK!', server_id) raise ConnectionResetError(0, 'hxs: bad certificate') if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest(): try: ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo) shared_secret = ecc.get_dh_key(server_key) self.logger.debug('hxs key exchange success') if mode == 1: self.__cipher = EncryptorStream(shared_secret, 'rc4-md5', check_iv=False) else: self.__cipher = AEncryptor(shared_secret, self.method, CTX, check_iv=False) # start reading from connection self._connection_task = asyncio.ensure_future( self.read_from_connection()) self._connection_stat = asyncio.ensure_future(self.stat()) self.connected = True self._socport = self.remote_writer.get_extra_info( 'sockname')[1] return except InvalidSignature: self.logger.error( 'hxs getKey Error: server auth failed, bad signature') else: self.logger.error( 'hxs getKey Error: server auth failed, bad username or password' ) else: self.logger.error('hxs getKey Error. bad password or timestamp.') raise ConnectionResetError(0, 'hxs getKey Error') async def _rfile_read(self, size, timeout=3): fut = self.remote_reader.readexactly(size) data = await asyncio.wait_for(fut, timeout=timeout) return data def count(self): return len(self._client_writer) async def stat(self): while not self.connection_lost: await asyncio.sleep(1) self._recv_tp_ewma = self._recv_tp_ewma * 0.8 + self._stat_recv_tp * 0.2 if self._recv_tp_ewma > self._recv_tp_max: self._recv_tp_max = self._recv_tp_ewma self._stat_recv_tp = 0 self._sent_tp_ewma = self._sent_tp_ewma * 0.8 + self._stat_sent_tp * 0.2 if self._sent_tp_ewma > self._sent_tp_max: self._sent_tp_max = self._sent_tp_ewma self._stat_sent_tp = 0 buffer_size = self.remote_writer.transport.get_write_buffer_size() self._buffer_size_ewma = self._buffer_size_ewma * 0.8 + buffer_size * 0.2 def busy(self): return self._recv_tp_ewma + self._sent_tp_ewma def is_busy(self): return self._buffer_size_ewma > 2048 or \ (self._recv_tp_max > 524288 and self._recv_tp_ewma > self._recv_tp_max * 0.3) or \ (self._sent_tp_max > 262144 and self._sent_tp_ewma > self._sent_tp_max * 0.3) def print_status(self): if not self.connected: return self.logger.info('%s:%s status:', self.name, self._socport) self.logger.info('recv_tp_max: %8d, ewma: %8d', self._recv_tp_max, self._recv_tp_ewma) self.logger.info('sent_tp_max: %8d, ewma: %8d', self._sent_tp_max, self._sent_tp_ewma) self.logger.info('buffer_ewma: %8d, stream: %6d', self._buffer_size_ewma, self.count()) async def close_stream(self, stream_id): if not self._client_resume_reading[stream_id].is_set(): self._client_resume_reading[stream_id].set() if self._stream_status[stream_id] != CLOSED: self.send_frame(RST_STREAM, 0, stream_id, bytes(random.randint(8, 256))) self._stream_status[stream_id] = CLOSED if stream_id in self._client_writer: writer = self._client_writer[stream_id] del self._client_writer[stream_id] if not writer.is_closing(): writer.close() try: await writer.wait_closed() except ConnectionError: pass async def client_writer_drain(self, stream_id): if self._settings_async_drain: asyncio.ensure_future(self.async_drain(stream_id)) else: await self._client_writer[stream_id].drain() async def async_drain(self, stream_id): if isinstance(self._client_writer[stream_id], UDPRelayHxs2): return wbuffer_size = self._client_writer[ stream_id].transport.get_write_buffer_size() if wbuffer_size <= CLIENT_WRITE_BUFFER: return async with self._client_drain_lock[stream_id]: try: # tell client to stop reading self.send_frame(WINDOW_UPDATE, 1, stream_id, bytes(random.randint(64, 256))) await self._client_writer[stream_id].drain() # tell client to resume reading self.send_frame(WINDOW_UPDATE, 0, stream_id, bytes(random.randint(64, 256))) except OSError: await self.close_stream(stream_id) return async def close(self): if self.remote_writer: if not self.remote_writer.is_closing(): self.remote_writer.close() try: await self.remote_writer.wait_closed() except OSError: pass
class Hxs2Connection: bufsize = 32768 def __init__(self, proxy, timeout, manager): if not isinstance(proxy, ParentProxy): proxy = ParentProxy(proxy, proxy) self.logger = logging.getLogger('hxs2') self.proxy = proxy self.name = self.proxy.name self.timeout = timeout self._manager = manager self._ping_test = False self._ping_time = 0 self.connected = False self.connection_lost = False self._psk = self.proxy.query.get('PSK', [''])[0] self.method = self.proxy.query.get('method', [DEFAULT_METHOD])[0].lower() self.hash_algo = self.proxy.query.get('hash', [DEFAULT_HASH])[0].upper() self.remote_reader = None self.remote_writer = None self.__pskcipher = None self.__cipher = None self._next_stream_id = 1 self._client_writer = {} self._client_status = {} self._stream_status = {} self._last_active = {} self._last_active_c = time.monotonic() self._last_ping_log = 0 self._last_direction = SEND self._last_count = 0 self.send_delay = 0 self.recv_intv = 1 self.recv_time = 0 self.recv_tp = 0 self.recv_tp_ewma = 0 self._stat_data_recv = 0 self._stat_total_recv = 1 self._stat_recv_tp = 0 self._stat_data_sent = 0 self._stat_total_sent = 1 self._lock = Lock() async def connect(self, addr, port, timeout=3): self.logger.debug('hxsocks2 send connect request') self.timeout = timeout async with self._lock: if self.connection_lost: self._manager.remove(self) raise ConnectionResetError(0, 'hxs connection lost') if not self.connected: try: await self.get_key() except asyncio.CancelledError: raise except Exception as err: self.logger.error('%s get_key %r', self.name, err) # self.logger.error(traceback.format_exc()) try: self.remote_writer.close() except (OSError, AttributeError): pass raise ConnectionResetError(0, 'hxs get_key failed.') # send connect request payload = b''.join([chr(len(addr)).encode('latin1'), addr.encode(), struct.pack('>H', port), b'\x00' * random.randint(64, 255), ]) stream_id = self._next_stream_id self._next_stream_id += 1 if self._next_stream_id > MAX_STREAM_ID: self.logger.error('MAX_STREAM_ID reached') self._manager.remove(self) await self.send_frame(1, 0, stream_id, payload) # wait for server response event = Event() self._client_status[stream_id] = event # await event.wait() fut = event.wait() try: await asyncio.wait_for(fut, timeout=timeout) except asyncio.TimeoutError: self.logger.error('no response from %s, timeout=%.3f', self.name, timeout) del self._client_status[stream_id] self.print_status() await self.send_ping() raise del self._client_status[stream_id] if self._stream_status[stream_id] == OPEN: socketpair_a, socketpair_b = socket.socketpair() if sys.platform == 'win32': socketpair_a.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536) socketpair_b.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536) reader, writer = await asyncio.open_connection(sock=socketpair_b) writer.transport.set_write_buffer_limits(0, 0) self._client_writer[stream_id] = writer self._last_active[stream_id] = time.monotonic() # start forwarding asyncio.ensure_future(self.read_from_client(stream_id, reader)) return socketpair_a raise ConnectionResetError(0, 'remote connect to %s:%d failed.' % (addr, port)) async def read_from_client(self, stream_id, client_reader): self.logger.debug('start read from client') while not self.connection_lost: try: intv = 5 fut = client_reader.read(self.bufsize) try: data = await asyncio.wait_for(fut, timeout=intv) self._last_active[stream_id] = time.monotonic() except asyncio.TimeoutError: if time.monotonic() - self._last_active[stream_id] > 60 or\ self._stream_status[stream_id] & EOF_RECV: await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) self._stream_status[stream_id] = CLOSED break continue except ConnectionResetError: await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) self._stream_status[stream_id] = CLOSED break if not data: # close stream(LOCAL) self._stream_status[stream_id] |= EOF_SENT await self.send_frame(1, 1, stream_id, b'\x00' * random.randint(8, 256)) break elif self._stream_status[stream_id] & EOF_SENT: self.logger.error('data recv from client, while stream is closed!') await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) self._stream_status[stream_id] = CLOSED self._client_writer[stream_id].close() return payload = struct.pack('>H', len(data)) + data + b'\x00' * random.randint(8, 256) await self.send_frame(0, 0, stream_id, payload) self._stat_data_sent += len(data) except asyncio.CancelledError: raise except Exception as err: self.logger.error('CLIENT_SIDE BOOM! %r', err) self.logger.error(traceback.format_exc()) self._stream_status[stream_id] = CLOSED break await asyncio.sleep(5) if self._stream_status[stream_id] != CLOSED: self._stream_status[stream_id] = CLOSED await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) if stream_id in self._client_writer: try: self._client_writer[stream_id].close() except OSError: pass del self._client_writer[stream_id] async def send_frame(self, type_, flags, stream_id, payload): self.logger.debug('send_frame type: %d, stream_id: %d', type_, stream_id) if self.connection_lost: self.logger.error('send_frame: connection closed. %s', self.name) return if type_ != 6: self._last_active_c = time.monotonic() if self._last_direction == RECV: self._last_direction = SEND self._last_count = 0 async with self._lock: try: header = struct.pack('>BBH', type_, flags, stream_id) data = header + payload ct = self.__cipher.encrypt(data) self.remote_writer.write(struct.pack('>H', len(ct)) + ct) await self.remote_writer.drain() self._stat_total_sent += len(ct) + 2 self._last_count += 1 except ConnectionResetError: self.connection_lost = True self._manager.remove(self) else: if type_ == 0 and self._last_count > 10 and random.random() < 0.01: asyncio.ensure_future(self.send_ping(False)) async def send_ping(self, test=True): if self._ping_time == 0: self._ping_test = test self._ping_time = time.monotonic() await self.send_frame(6, 0, 0, b'\x00' * random.randint(64, 256)) async def read_from_connection(self): self.logger.debug('start read from connection') last_recv = time.monotonic() while not self.connection_lost: try: # read frame_len intv = 3 if self._ping_test else 6 try: frame_len = await self._rfile_read(2, timeout=intv) frame_len, = struct.unpack('>H', frame_len) except asyncio.TimeoutError: if self._ping_test: self.logger.warning('server no response %s', self.proxy.name) break if time.monotonic() - self._last_active_c > 120: # no point keeping so long break if time.monotonic() - self._last_active_c > 10: await self.send_ping() continue except Exception as err: # destroy connection self.logger.error('read from connection error: %r', err) break finally: # log recv delay recv_intv = time.monotonic() - last_recv last_recv = time.monotonic() self.recv_intv = self.recv_intv * 0.87 + recv_intv * 0.13 # read frame_data try: frame_data = await self._rfile_read(frame_len, timeout=self.timeout) frame_data = self.__cipher.decrypt(frame_data) self._stat_total_recv += frame_len + 2 self._stat_recv_tp += frame_len + 2 except (asyncio.TimeoutError, InvalidTag) as err: # destroy connection self.logger.error('read frame data error: %r', err) break else: recv_time = time.monotonic() - last_recv self.recv_time = self.recv_time * 0.87 + recv_time * 0.13 # parse chunk_data # +------+-------------------+----------+ # | type | flags | stream_id | payload | # +------+-------------------+----------+ # | 1 | 1 | 2 | Variable | # +------+-------------------+----------+ header, payload = frame_data[:4], frame_data[4:] frame_type, frame_flags, stream_id = struct.unpack('>BBH', header) payload = io.BytesIO(payload) self.logger.debug('recv frame_type: %s, stream_id: %s', frame_type, stream_id) if self._last_direction == SEND: self._last_direction = RECV self._last_count = 0 self._last_count += 1 if self._last_count > 10 and random.random() < 0.01: await self.send_frame(6, 1, 0, b'\x00' * random.randint(64, 256)) if frame_type == 0: # DATA # first 2 bytes of payload indicates data_len, the rest would be padding self._last_active_c = time.monotonic() data_len, = struct.unpack('>H', payload.read(2)) data = payload.read(data_len) if len(data) != data_len: # something went wrong, destory connection self.logger.error('len(data) != data_len') break # sent data to stream try: self._last_active[stream_id] = time.monotonic() self._client_writer[stream_id].write(data) await self._client_writer[stream_id].drain() self._stat_data_recv += data_len except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError): # client error, reset stream self._client_writer[stream_id].close() self._stream_status[stream_id] = CLOSED await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) except KeyError: self._stream_status[stream_id] = CLOSED await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) elif frame_type == 1: # HEADER self._last_active_c = time.monotonic() if self._next_stream_id == stream_id: # server is not supposed to open a new stream # send connection error? break elif stream_id < self._next_stream_id: if frame_flags == END_STREAM_FLAG: self._stream_status[stream_id] |= EOF_RECV try: self._client_writer[stream_id].write_eof() # KeyError? except (KeyError, AttributeError, ConnectionResetError): pass if self._stream_status[stream_id] == CLOSED: if stream_id in self._client_writer: self._client_writer[stream_id].close() del self._client_writer[stream_id] else: # confirm a stream is opened if stream_id in self._client_status: self._stream_status[stream_id] = OPEN self._client_status[stream_id].set() else: self.logger.info('%s stream open, client closed', self.name) self._stream_status[stream_id] = CLOSED await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256)) elif frame_type == 3: # RST_STREAM self._last_active_c = time.monotonic() self._stream_status[stream_id] = CLOSED if stream_id in self._client_writer: self._client_writer[stream_id].close() del self._client_writer[stream_id] elif frame_type == 6: # PING if frame_flags == 1: # pong if time.monotonic() - self._last_ping_log > 30: resp_time = time.monotonic() - self._ping_time self.logger.info('server response time: %.3f %s', resp_time, self.proxy.name) self._last_ping_log = time.monotonic() self._ping_test = False self._ping_time = 0 else: await self.send_frame(6, 1, 0, b'\x00' * random.randint(64, 256)) elif frame_type == 7: # GOAWAY # no more new stream max_stream_id = payload.read(2) self._manager.remove(self) for stream_id, client_writer in self._client_writer: if stream_id > max_stream_id: # reset stream try: client_writer.close() except OSError: pass elif frame_type == 8: # WINDOW_UPDATE pass else: break except Exception as err: self.logger.error('CONNECTION BOOM! %r', err) self.logger.error(traceback.format_exc()) break # out of loop, destroy connection self.connection_lost = True self.logger.warning('out of loop %s', self.proxy.name) self.logger.info('total_recv: %d, data_recv: %d %.3f', self._stat_total_recv, self._stat_data_recv, self._stat_data_recv / self._stat_total_recv) self.logger.info('total_sent: %d, data_sent: %d %.3f', self._stat_total_sent, self._stat_data_sent, self._stat_data_sent / self._stat_total_sent) self._manager.remove(self) for sid, status in self._client_status.items(): if isinstance(status, Event): self._stream_status[sid] = CLOSED status.set() try: self.remote_writer.close() except (OSError, IOError): pass for stream_id, writer in self._client_writer.items(): try: writer.close() except ConnectionResetError: pass async def get_key(self): self.logger.debug('hxsocks2 getKey') usn, psw = (self.proxy.username, self.proxy.password) self.logger.info('%s connect to server', self.name) from .connection import open_connection self.remote_reader, self.remote_writer, _ = await open_connection( self.proxy.hostname, self.proxy.port, proxy=self.proxy.get_via(), timeout=self.timeout, tunnel=True) # prep key exchange request self.__pskcipher = Encryptor(self._psk, self.method) ecc = ECC(self.__pskcipher._key_len) pubk = ecc.get_pub_key() ts = int(time.time()) // 30 ts = struct.pack('>I', ts) padding_len = random.randint(64, 255) data = b''.join([chr(len(pubk)).encode('latin1'), pubk, hmac.new(psw.encode(), ts + pubk + usn.encode(), hashlib.sha256).digest(), b'\x00' * padding_len]) data = chr(20).encode() + struct.pack('>H', len(data)) + data ct = self.__pskcipher.encrypt(data) # send key exchange request self.remote_writer.write(ct) await self.remote_writer.drain() # read iv iv = await self._rfile_read(self.__pskcipher._iv_len, self.timeout) self.__pskcipher.decrypt(iv) # read server response if is_aead(self.method): ct_len = await self._rfile_read(18, self.timeout) ct_len = self.__pskcipher.decrypt(ct_len) ct_len, = struct.unpack('!H', ct_len) ct = await self._rfile_read(ct_len + 16) ct = self.__pskcipher.decrypt(ct) data = ct[2:] else: resp_len = await self._rfile_read(2, self.timeout) resp_len = self.__pskcipher.decrypt(resp_len) resp_len, = struct.unpack('>H', resp_len) data = await self._rfile_read(resp_len) data = self.__pskcipher.decrypt(data) data = io.BytesIO(data) resp_code = byte2int(data.read(1)) if resp_code == 0: self.logger.debug('hxsocks read key exchange respond') pklen = byte2int(data.read(1)) scertlen = byte2int(data.read(1)) siglen = byte2int(data.read(1)) server_key = data.read(pklen) auth = data.read(32) server_cert = data.read(scertlen) signature = data.read(siglen) # TODO: ask user if a certificate should be accepted or not. host, port = self.proxy._host_port server_id = '%s_%d' % (host, port) if server_id not in KNOWN_HOSTS: self.logger.info('hxs: server %s new cert %s saved.', server_id, hashlib.sha256(server_cert).hexdigest()[:8]) with open('./.hxs_known_hosts/' + server_id + '.cert', 'wb') as f: f.write(server_cert) KNOWN_HOSTS[server_id] = server_cert elif KNOWN_HOSTS[server_id] != server_cert: self.logger.error('hxs: server %s certificate mismatch! PLEASE CHECK!', server_id) raise ConnectionResetError(0, 'hxs: bad certificate') if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest(): try: ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo) shared_secret = ecc.get_dh_key(server_key) self.logger.debug('hxs key exchange success') self.__cipher = AEncryptor(shared_secret, self.method, CTX) # start reading from connection asyncio.ensure_future(self.read_from_connection()) asyncio.ensure_future(self.stat()) self.connected = True return except InvalidSignature: self.logger.error('hxs getKey Error: server auth failed, bad signature') else: self.logger.error('hxs getKey Error: server auth failed, bad username or password') else: self.logger.error('hxs getKey Error. bad password or timestamp.') raise ConnectionResetError(0, 'hxs getKey Error') async def _rfile_read(self, size, timeout=3): fut = self.remote_reader.readexactly(size) data = await asyncio.wait_for(fut, timeout=timeout) return data def count(self): return len(self._client_writer) async def stat(self): while not self.connection_lost: await asyncio.sleep(1) self.recv_tp = self._stat_recv_tp self.recv_tp_ewma = self.recv_tp_ewma * 0.87 + self._stat_recv_tp * 0.13 self._stat_recv_tp = 0 def is_busy(self): if self.recv_time > self.recv_intv * 0.8: return True return False def print_status(self): self.logger.info('%s recv_tp: %s', self.name, self.recv_tp) self.logger.info('%s tp_ewma: %d', self.name, self.recv_tp_ewma) self.logger.info('%s recv_intv: %f', self.name, self.recv_intv) self.logger.info('%s recv_time: %f', self.name, self.recv_time) self.logger.info('%s stream: %d', self.name, self.count())
class HXsocksHandler: bufsize = 32768 def __init__(self, server): self.server = server self.logger = server.logger self.user_mgr = self.server.user_mgr self.address = self.server.address self.encryptor = Encryptor(self.server.psk, self.server.method) self._buf = b'' self.client_address = None self.client_reader = None async def _read(self, size=None): if self.server.aead: _len = await self.client_reader.readexactly(18) if not _len: return b'' _len = self.encryptor.decrypt(_len) _len, = struct.unpack("!H", _len) ct = await self.client_reader.readexactly(_len + 16) if not ct: return b'' else: size = size or self.bufsize ct = await self.client_reader.read(size) return self.encryptor.decrypt(ct) async def read(self, size=None): # compatible with shadowsocks aead if not size: if self._buf: buf, self._buf = self._buf, b'' return buf return await self._read() while len(self._buf) < size: self._buf += (await self._read(size - len(self._buf))) _buf, self._buf = self._buf[:size], self._buf[size:] return _buf async def handle(self, client_reader, client_writer): try: await self._handle(client_reader, client_writer) except Exception as err: self.logger.error('HXsocksHandler.handle') self.logger.error(repr(err)) self.logger.error(traceback.format_exc()) client_writer.close() async def _handle(self, client_reader, client_writer): self.client_address = client_writer.get_extra_info('peername')[0] self.client_reader = client_reader self.logger.debug('incoming connection %s', self.client_address) # read iv try: fut = self.client_reader.readexactly(self.encryptor._iv_len) iv_ = await asyncio.wait_for(fut, timeout=10) self.encryptor.decrypt(iv_) except IVError: self.logger.error('iv reused, %s', self.client_address) await self.play_dead() return except (asyncio.TimeoutError, asyncio.IncompleteReadError, ConnectionResetError): self.logger.warning('iv read failed, %s', self.client_address) return # read cmd try: fut = self.read(1) cmd = await asyncio.wait_for(fut, timeout=10) except asyncio.TimeoutError: self.logger.debug('read cmd timed out. %s', self.client_address) return except (ConnectionResetError, asyncio.IncompleteReadError): self.logger.debug('read cmd reset. %s', self.client_address) return except InvalidTag: self.logger.error('InvalidTag while read cmd. %s', self.client_address) await self.play_dead() return cmd = cmd[0] self.logger.debug('cmd: %s %s', cmd, self.client_address) if cmd in (1, 3, 4): # A shadowsocks request result = await self.handle_ss(client_writer, addr_type=cmd) if result: await self.play_dead() return if cmd == 20: # hxsocks2 client key exchange rint = random.randint(64, 2048) req_len = await self.read(2) req_len, = struct.unpack('>H', req_len) data = await self.read(req_len) data = io.BytesIO(data) ts = int(time.time()) // 30 pklen = data.read(1)[0] client_pkey = data.read(pklen) client_auth = data.read(32) def _send(data): if self.encryptor._encryptor: data = struct.pack('>H', len(data)) + data ct = self.encryptor.encrypt(data) client_writer.write(struct.pack('>H', len(ct)) + ct) else: data = struct.pack('>H', len(data)) + data client_writer.write(self.encryptor.encrypt(data)) def auth(): for _ts in [ts, ts - 1, ts + 1]: for user, passwd in self.user_mgr.iter_user(): h = hmac.new( passwd.encode(), struct.pack('>I', _ts) + client_pkey + user.encode(), hashlib.sha256).digest() if compare_digest(h, client_auth): return user return None client = auth() if not client: self.logger.error('user not found. %s', self.client_address) await self.play_dead() return try: pkey, passwd = self.user_mgr.key_xchange( client, client_pkey, self.encryptor._key_len) self.logger.info('new key exchange. client: %s %s', client, self.client_address) h = hmac.new(passwd.encode(), client_pkey + pkey + client.encode(), hashlib.sha256).digest() scert = self.user_mgr.SERVER_CERT.get_pub_key() signature = self.user_mgr.SERVER_CERT.sign(h, DEFAULT_HASH) data = bytes( (0, len(pkey), len(scert), len(signature) )) + pkey + h + scert + signature + os.urandom(rint) _send(data) client_pkey = hashlib.md5(client_pkey).digest() conn = Hxs2Connection( client_reader, client_writer, client, self.user_mgr.get_skey_by_pubkey(client_pkey), self.server.method, self.server.proxy, self.user_mgr, self.address[1], self.logger) await conn.wait_close() self.user_mgr.del_key(client_pkey) return except ValueError as err: self.logger.error('key exchange failed. %s %s', self.client_address, err) await self.play_dead() return # TODO: security log self.logger.error('bad cmd: %s, %s', cmd, self.client_address) await self.play_dead() return async def play_dead(self, timeout=1): count = random.randint(6, 15) for _ in range(count): fut = self.client_reader.read(self.bufsize) try: await asyncio.wait_for(fut, timeout) except (asyncio.TimeoutError, ConnectionResetError): return async def handle_ss(self, client_writer, addr_type): # if error, return 1 # get header... try: assert addr_type in (1, 3, 4) if addr_type == 1: addr = await self.read(4) addr = socket.inet_ntoa(addr) elif addr_type == 3: data = await self.read(1) addr = await self.read(data[0]) addr = addr.decode('ascii') else: data = await self.read(16) addr = socket.inet_ntop(socket.AF_INET6, data) port = await self.read(2) port, = struct.unpack('>H', port) except Exception as err: self.logger.error('error on read ss header: %s %s', err, self.client_address) self.logger.error(traceback.format_exc()) return 1 # access control try: self.user_mgr.user_access_ctrl(self.address[1], addr, self.client_address) except ValueError as err: self.logger.error('access denied! %s:%s, %s %s', addr, port, err) return # create connection self.logger.info('connect to %s:%d %r', addr, port, self.client_address) try: remote_reader, remote_writer = await open_connection( addr, port, self.server.proxy) except Exception as err: self.logger.error('connect to %s:%s failed! %r', addr, port, err) return # forward context = ForwardContext() tasks = [ self.ss_forward_a(remote_writer, context), self.ss_forward_b(remote_reader, client_writer, self.encryptor.encrypt, context), ] try: await asyncio.wait(tasks) except Exception as err: self.logger.error(repr(err)) self.logger.error(traceback.format_exc()) remote_writer.close() # access log traffic = (context.traffic_from_client, context.traffic_from_remote) self.user_mgr.user_access_log(self.address[1], addr, traffic, self.client_address) async def ss_forward_a(self, write_to, context, timeout=60): # data from ss client, decrypt, sent to remote while True: try: fut = self.read() data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time( ) - context.last_active > timeout or context.remote_eof: data = b'' else: continue except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, ConnectionResetError, OSError): data = b'' if not data: break context.traffic_from_client += len(data) try: write_to.write(data) await write_to.drain() except ConnectionResetError: context.local_eof = True return context.local_eof = True try: write_to.write_eof() except (ConnectionResetError, OSError): pass async def ss_forward_b(self, read_from, write_to, cipher, context, timeout=60): # data from remote, encrypt, sent to client while True: try: fut = read_from.read(self.bufsize) data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time( ) - context.last_active > timeout or context.local_eof: data = b'' else: continue except (ConnectionResetError, OSError): data = b'' if not data: break context.traffic_from_remote += len(data) data = cipher(data) try: write_to.write(data) await write_to.drain() except ConnectionResetError: context.remote_eof = True return context.remote_eof = True
class SSConn: bufsize = 65535 tcp_timeout = 600 def __init__(self, proxy, ): self.logger = logging.getLogger('ss') self.proxy = proxy ssmethod, sspassword = self.proxy.username, self.proxy.password if sspassword is None: ssmethod, sspassword = base64.b64decode(ssmethod).decode().split(':', 1) ssmethod = ssmethod.lower() self._address = None self._port = 0 self.client_reader = None self.client_writer = None self.remote_reader = None self.remote_writer = None self.task = None self.aead = is_aead(ssmethod) self.crypto = Encryptor(sspassword, ssmethod) self.connected = False self.last_active = time.time() # if eof recieved self.remote_eof = False self.client_eof = False self.data_recved = False self._buf = b'' async def connect(self, addr, port, timeout, limit, tcp_nodelay): self._address = addr self._port = port from .connection import open_connection self.remote_reader, self.remote_writer, _ = await open_connection( self.proxy.hostname, self.proxy.port, proxy=self.proxy.get_via(), timeout=timeout, tunnel=True, limit=131072, tcp_nodelay=tcp_nodelay) # self.remote_writer.transport.set_write_buffer_limits(262144) # create socket_pair sock_a, sock_b = socket.socketpair() if sys.platform == 'win32': sock_a.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock_a.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.client_reader, self.client_writer = await asyncio.open_connection(sock=sock_b) self.client_writer.transport.set_write_buffer_limits(262144) # start forward self.task = asyncio.ensure_future(self.forward()) # return reader, writer reader, writer = await asyncio.open_connection(sock=sock_a, limit=limit) return reader, writer async def forward(self): tasks = [asyncio.create_task(self.forward_from_client()), asyncio.create_task(self.forward_from_remote()), ] await asyncio.wait(tasks) for writer in (self.remote_writer, self.client_writer): try: writer.close() await writer.wait_closed() except ConnectionError: pass async def forward_from_client(self): # read from client, encrypt, sent to server while True: fut = self.client_reader.read(self.bufsize) try: data = await asyncio.wait_for(fut, timeout=6) self.last_active = time.time() except asyncio.TimeoutError: if time.time() - self.last_active > self.tcp_timeout or self.remote_eof: data = b'' else: continue except OSError: data = b'' if not data: break if not self.connected: header = b''.join([chr(3).encode(), chr(len(self._address)).encode('latin1'), self._address.encode(), struct.pack(b">H", self._port)]) data = header + data self.connected = True self.remote_writer.write(self.crypto.encrypt(data)) try: await self.remote_writer.drain() except ConnectionError: break self.client_eof = True try: self.remote_writer.write_eof() except ConnectionError: pass async def _read(self): if self.aead: fut = self.remote_reader.readexactly(18) _len = await asyncio.wait_for(fut, timeout=6) if not _len: return b'' _len = self.crypto.decrypt(_len) _len, = struct.unpack("!H", _len) fut = self.remote_reader.readexactly(_len + 16) try: ct = await asyncio.wait_for(fut, timeout=2) except asyncio.TimeoutError as err: raise IncompleteChunk() from err if not ct: return b'' else: ct = await self.remote_reader.read(self.bufsize) return self.crypto.decrypt(ct) async def forward_from_remote(self): # read from remote, decrypt, sent to client try: fut = self.remote_reader.readexactly(self.crypto.iv_len) iv_ = await asyncio.wait_for(fut, timeout=12) self.crypto.decrypt(iv_) except (asyncio.TimeoutError, asyncio.IncompleteReadError): self.remote_eof = True try: self.client_writer.write_eof() except ConnectionError: pass return while True: try: data = await self._read() self.last_active = time.time() self.data_recved = True except asyncio.TimeoutError: if time.time() - self.last_active > self.tcp_timeout or self.client_eof: data = b'' else: continue except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, IncompleteChunk): data = b'' if not data: break try: self.client_writer.write(data) await self.client_writer.drain() except ConnectionError: break self.remote_eof = True try: self.client_writer.write_eof() except ConnectionError: pass
class HXsocksHandler: bufsize = 8192 def __init__(self, server): self.server = server self.logger = server.logger self.encryptor = Encryptor(self.server.PSK, self.server.method) self._buf = b'' async def _read(self, size=None): if self.server.aead: _len = await self.client_reader.readexactly(18) if not _len: return b'' _len = self.encryptor.decrypt(_len) _len, = struct.unpack("!H", _len) ct = await self.client_reader.readexactly(_len + 16) if not ct: return b'' else: size = size or self.bufsize ct = await self.client_reader.read(size) return self.encryptor.decrypt(ct) async def read(self, size=None): # compatible with shadowsocks aead if not size: if self._buf: buf, self._buf = self._buf, b'' return buf else: return await self._read() else: while len(self._buf) < size: self._buf += (await self._read(size - len(self._buf))) _buf, self._buf = self._buf[:size], self._buf[size:] return _buf async def handle(self, client_reader, client_writer): try: await self._handle(client_reader, client_writer) except Exception as e: self.logger.error(repr(e)) self.logger.error(traceback.format_exc()) client_writer.close() async def _handle(self, client_reader, client_writer): self.client_address = client_writer.get_extra_info('peername') self.client_reader = client_reader self.logger.debug('incoming connection {}'.format(self.client_address)) KM = self.server.kmgr try: fut = self.client_reader.readexactly(self.encryptor._iv_len) iv = await asyncio.wait_for(fut, timeout=10) self.encryptor.decrypt(iv) except IVError: self.logger.error('iv reused, {}'.format(self.client_address)) await self.play_dead() return except (asyncio.TimeoutError, asyncio.IncompleteReadError, ConnectionResetError): self.logger.warning('iv read failed, {}'.format(self.client_address)) return req_count = 0 while True: if req_count: # Not shadowsocks request self.logger.debug('excepting next request: {}'.format(self.client_address)) fut = client_reader.readexactly(2) try: await asyncio.wait_for(fut, timeout=120) except (OSError, ConnectionResetError, asyncio.IncompleteReadError, asyncio.TimeoutError) as e: self.logger.debug('closed: {} {}'.format(e, self.client_address)) return try: fut = self.read(1) cmd = await asyncio.wait_for(fut, timeout=10) except asyncio.TimeoutError: self.logger.debug('read cmd timed out. {}'.format(self.client_address)) return except (ConnectionResetError, asyncio.IncompleteReadError): self.logger.debug('read cmd reset. {}'.format(self.client_address)) return except InvalidTag: self.logger.error('InvalidTag while read cmd. {}'.format(self.client_address)) await self.play_dead() return cmd = cmd[0] self.logger.debug('cmd: {} {}'.format(cmd, self.client_address)) if cmd in (1, 3, 4): # A shadowsocks request result = await self.handle_ss(client_reader, client_writer, addr_type=cmd) if result: await self.play_dead() return elif cmd in (10, 20): # hxsocks / hxsocks2 client key exchange req_count += 1 rint = random.randint(64, 2048) req_len = await self.read(2) req_len, = struct.unpack('>H', req_len) data = await self.read(req_len) data = io.BytesIO(data) ts = int(time.time()) // 30 pklen = data.read(1)[0] client_pkey = data.read(pklen) client_auth = data.read(32) def _send(data): if self.encryptor._encryptor: data = struct.pack('>H', len(data)) + data ct = self.encryptor.encrypt(data) client_writer.write(struct.pack('>H', len(ct)) + ct) return data = struct.pack('>H', len(data)) + data client_writer.write(self.encryptor.encrypt(data)) def auth(): for _ts in [ts, ts - 1, ts + 1]: for user, passwd in KM.iter_user(): h = hmac.new(passwd.encode(), struct.pack('>I', _ts) + client_pkey + user.encode(), hashlib.sha256).digest() if compare_digest(h, client_auth): return user client = auth() if not client: self.logger.error('user not found. {}'.format(self.client_address)) await self.play_dead() return pkey, passwd = KM.key_xchange(client, client_pkey, self.encryptor._key_len) if pkey: self.logger.info('new key exchange. client: {} {}'.format(client, self.client_address)) h = hmac.new(passwd.encode(), client_pkey + pkey + client.encode(), hashlib.sha256).digest() scert = KM.SERVER_CERT.get_pub_key() signature = KM.SERVER_CERT.sign(h, DEFAULT_HASH) data = bytes((0, len(pkey), len(scert), len(signature))) + pkey + h + scert + signature + os.urandom(rint) _send(data) if cmd == 20: client_pkey = hashlib.md5(client_pkey).digest() conn = hxs2_connection(client_reader, client_writer, KM.get_skey_by_pubkey(client_pkey), self.server.method, self.server.proxy, self.logger) await conn.wait_close() return continue else: self.logger.error('Private_key already registered. client: {} {}'.format(client, self.client_address)) await self.play_dead() return elif cmd == 11: # a connect request req_count += 1 client_pkey = await self.read(16) rint = random.randint(64, 2048) def _send(code, cipher): if code == 1: client_writer.write(struct.pack('>H', rint) + os.urandom(rint)) else: ct = cipher.encrypt(bytes((code, )) + os.urandom(rint)) client_writer.write(struct.pack('>H', len(ct)) + ct) if KM.check_key(client_pkey): self.logger.error('client key not exist or expired. {}'.format(self.client_address)) ctlen = await self.read(2) ctlen, = struct.unpack('>H', ctlen) await self.read(ctlen) _send(1, None) continue user = KM.get_user_by_pubkey(client_pkey) cipher = AEncryptor(KM.get_skey_by_pubkey(client_pkey), self.server.method, CTX) ctlen = await self.read(2) ctlen, = struct.unpack('>H', ctlen) ct = await self.read(ctlen) try: data = cipher.decrypt(ct) except InvalidTag: self.logger.error('hxs connect req InvalidTag. {} {}'.format(user, self.client_address)) # await self.play_dead() return buf = io.BytesIO(data) ts = buf.read(4) if abs(struct.unpack('>I', ts)[0] - time.time()) > 600: self.logger.error('bad timestamp, possible replay attrack. {} {}'.format(user, self.client_address)) # KM.del_key(client_pkey) # _send(1, None) await self.play_dead() return host_len = buf.read(1)[0] addr = buf.read(host_len).decode('ascii') port, = struct.unpack('>H', buf.read(2)) self.logger.info('connecting to {}:{} via {}, {} {} {}'.format(addr, port, self.server.proxy, user, req_count, self.client_address)) try: remote_reader, remote_writer = await open_connection(addr, port, self.server.proxy) _send(0, cipher) except Exception: self.logger.error('connect to {}:{} failed!'.format(addr, port)) _send(2, cipher) continue context = ForwardContext() tasks = [self.hxs_forward_from_remote(remote_reader, client_writer, cipher, context), self.hxs_forward_from_client(client_reader, client_writer, remote_writer, cipher, context), ] await asyncio.wait(tasks) remote_writer.close() if context.readable or context.writeable: return continue elif cmd == 12: # get public key req_len = await self.read(2) req_len, = struct.unpack('>H', req_len) data = await self.read(req_len) # drop data # return public key with padding rint = random.randint(64, 2048) scert = KM.SERVER_CERT.get_pub_key() data = struct.pack('>H', len(scert)) + scert + os.urandom(rint) data = struct.pack('>H', len(data)) + data # the first response, just encrypt and sent client_writer.write(self.encryptor.encrypt(data)) continue else: # TODO: security self.logger.error('bad cmd: %s, %s' % (cmd, self.client_address)) await self.play_dead() return async def play_dead(self, timeout=1): for _ in range(10): fut = self.client_reader.read(self.bufsize) try: await asyncio.wait_for(fut, timeout=1) except (asyncio.TimeoutError, ConnectionResetError): return async def hxs_forward_from_remote(self, remote_reader, client_writer, cipher, context, timeout=120): # read from remote_reader, write to client_writer total_send = 0 while not context.remote_eof: try: fut = remote_reader.read(self.bufsize) data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time() - context.last_active > timeout or context.local_eof: data = b'' else: continue except (ConnectionResetError, OSError): data = b'' if not data: # timeout or remote closed... context.remote_eof = True if total_send < 8196 and random.random() < 0.5: # sent fake chunk before close _data = bytes((2, )) + b'\x00' * random.randint(1024, 8196) ct = cipher.encrypt(_data) _data = struct.pack('>H', len(ct)) + ct client_writer.write(_data) # send data / close link total_send += len(data) padding_len = random.randint(8, 255) data = bytes((padding_len, )) + data + b'\x00' * padding_len ct = cipher.encrypt(data) data = struct.pack('>H', len(ct)) + ct try: client_writer.write(data) await client_writer.drain() except ConnectionResetError: context.local_eof = True return context.writeable = False async def hxs_forward_from_client(self, client_reader, client_writer, remote_writer, cipher, context, timeout=200): # data from hxs client remote_writable = True while not context.local_eof: try: fut = client_reader.readexactly(2) ct_len = await asyncio.wait_for(fut, timeout=10) # client is supposed to close hxs link ct_len, = struct.unpack('>H', ct_len) except asyncio.TimeoutError: if time.time() - context.last_active > timeout or context.remote_eof: # timeout, sent remote eof... remote_writer.write_eof() remote_writable = False break else: continue except (ConnectionResetError, OSError, asyncio.IncompleteReadError): context.local_eof = True break try: fut = client_reader.readexactly(ct_len) ct = await asyncio.wait_for(fut, timeout=5) data = cipher.decrypt(ct) pad_len = data[0] if 0 < pad_len < 8: # fake chunk, drop if pad_len == 1 and context.writeable: _data = bytes((2, )) + b'\x00' * random.randint(1024, 8196) ct = cipher.encrypt(_data) _data = struct.pack('>H', len(ct)) + ct client_writer.write(_data) continue except (asyncio.TimeoutError, BufEmptyError, asyncio.IncompleteReadError, ValueError, ConnectionResetError): context.local_eof = True remote_writer.write_eof() return data = data[1:0 - pad_len] if pad_len else data[1:] if data and remote_writable: context.last_active = time.time() remote_writer.write(data) await remote_writer.drain() # ConnectionResetError else: # client closed, gracefully context.readable = False try: remote_writer.write_eof() except ConnectionResetError: pass break context.local_eof = True async def handle_ss(self, client_reader, client_writer, addr_type): # if error, return 1 # get header... try: assert addr_type in (1, 3, 4) if addr_type & 15 == 1: addr = await self.read(4) addr = socket.inet_ntoa(addr) elif addr_type & 15 == 3: data = await self.read(1) addr = await self.read(data[0]) addr = addr.decode('ascii') else: data = await self.read(16) addr = socket.inet_ntop(socket.AF_INET6, data) port = await self.read(2) port, = struct.unpack('>H', port) except Exception as e: self.logger.error('error on read ss header: {} {}'.format(e, self.client_address)) self.logger.error(traceback.format_exc()) return 1 self.logger.info('connect to {}:{} {!r} {!r}'.format(addr, port, self.client_address, self.server.proxy)) try: remote_reader, remote_writer = await open_connection(addr, port, self.server.proxy) except Exception as e: self.logger.error('connect to {}:{} failed! {!r}'.format(addr, port, e)) return context = ForwardContext() tasks = [self.ss_forward_A(client_reader, remote_writer, self.encryptor.decrypt, context), self.ss_forward_B(remote_reader, client_writer, self.encryptor.encrypt, context), ] try: await asyncio.wait(tasks) except Exception as e: self.logger.error(repr(e)) self.logger.error(traceback.format_exc()) remote_writer.close() async def ss_forward_A(self, read_from, write_to, cipher, context, timeout=60): # data from ss client while True: try: fut = self.read() data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time() - context.last_active > timeout or context.remote_eof: data = b'' else: continue except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, ConnectionResetError, OSError): data = b'' if not data: break try: write_to.write(data) await write_to.drain() except ConnectionResetError: context.local_eof = True return context.local_eof = True try: write_to.write_eof() except (ConnectionResetError, OSError): pass async def ss_forward_B(self, read_from, write_to, cipher, context, timeout=60): # data from remote while True: try: fut = read_from.read(self.bufsize) data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time() - context.last_active > timeout or context.local_eof: data = b'' else: continue except (ConnectionResetError, OSError): data = b'' if not data: break data = cipher(data) try: write_to.write(data) await write_to.drain() except ConnectionResetError: context.remote_eof = True return context.remote_eof = True
class SSConn: bufsize = 32768 def __init__(self, proxy, sock_b): self.logger = logging.getLogger('ss') self.proxy = proxy self.sock_b = sock_b ssmethod, sspassword = self.proxy.username, self.proxy.password if sspassword is None: import base64 ssmethod, sspassword = base64.b64decode(ssmethod).decode().split( ':', 1) ssmethod = ssmethod.lower() self._address = None self._port = 0 self.client_reader = None self.client_writer = None self.remote_reader = None self.remote_writer = None self.aead = is_aead(ssmethod) self.crypto = Encryptor(sspassword, ssmethod) self.connected = False self.last_active = time.time() # if eof recieved self.remote_eof = False self.client_eof = False self.data_recved = False self._buf = b'' async def connect(self, addr, port, timeout): self._address = addr self._port = port self.client_reader, self.client_writer = await asyncio.open_connection( sock=self.sock_b) self.client_writer.transport.set_write_buffer_limits(0, 0) from .connection import open_connection self.remote_reader, self.remote_writer, _ = await open_connection( self.proxy.hostname, self.proxy.port, proxy=self.proxy.get_via(), timeout=timeout, tunnel=True) async def forward(self): tasks = [ self.forward_from_client(), self.forward_from_remote(), ] try: await asyncio.wait(tasks) except asyncio.CancelledError: raise except Exception as e: self.logger.error('SSConn.forward') self.logger.error(repr(e)) self.logger.error(traceback.format_exc()) for writer in (self.remote_writer, self.client_writer): try: writer.close() except ConnectionResetError: pass async def forward_from_client(self): # read from client, encrypt, sent to server while True: intv = 5 if self.data_recved else 1 fut = self.client_reader.read(self.bufsize) try: data = await asyncio.wait_for(fut, timeout=intv) self.last_active = time.time() except asyncio.TimeoutError: if time.time() - self.last_active > 60 or self.remote_eof: data = b'' else: continue except (ConnectionResetError, OSError, AttributeError): data = b'' if not data: break if not self.connected: header = b''.join([ chr(3).encode(), chr(len(self._address)).encode('latin1'), self._address.encode(), struct.pack(b">H", self._port) ]) data = header + data self.connected = True self.remote_writer.write(self.crypto.encrypt(data)) self.client_eof = True try: self.remote_writer.write_eof() except (ConnectionResetError, OSError, AttributeError): pass async def _read(self, size=None): if self.aead: _len = await self.remote_reader.readexactly(18) if not _len: return b'' _len = self.crypto.decrypt(_len) _len, = struct.unpack("!H", _len) fut = self.remote_reader.readexactly(_len + 16) try: ct = await asyncio.wait_for(fut, timeout=1) except asyncio.TimeoutError: raise IncompleteChunk() if not ct: return b'' else: size = size or self.bufsize ct = await self.remote_reader.read(size) return self.crypto.decrypt(ct) async def forward_from_remote(self): # read from remote, decrypt, sent to client try: fut = self.remote_reader.readexactly(self.crypto._iv_len) iv = await asyncio.wait_for(fut, timeout=60) self.crypto.decrypt(iv) except (asyncio.TimeoutError, asyncio.IncompleteReadError): self.remote_eof = True try: self.client_writer.write_eof() except (ConnectionResetError, OSError, AttributeError): pass return while True: try: fut = self._read() data = await asyncio.wait_for(fut, timeout=5) self.last_active = time.time() self.data_recved = True except asyncio.TimeoutError: if time.time() - self.last_active > 60 or self.client_eof: data = b'' else: continue except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, IncompleteChunk): data = b'' if not data: break try: self.client_writer.write(data) await self.client_writer.drain() except ConnectionResetError: break self.remote_eof = True try: self.client_writer.write_eof() except (ConnectionResetError, OSError, AttributeError): pass
class ShadowsocksHandler: bufsize = 8192 def __init__(self, server): self.server = server self.logger = server.logger self.encryptor = Encryptor(self.server.PSK, self.server.method) self._buf = b'' async def _read(self, size=None): if self.server.aead: _len = await self.client_reader.readexactly(18) if not _len: return b'' _len = self.encryptor.decrypt(_len) _len, = struct.unpack("!H", _len) ct = await self.client_reader.readexactly(_len + 16) if not ct: return b'' else: size = size or self.bufsize ct = await self.client_reader.read(size) return self.encryptor.decrypt(ct) async def read(self, size=None): # compatible with shadowsocks aead if not size: if self._buf: buf, self._buf = self._buf, b'' return buf else: return await self._read() else: while len(self._buf) < size: self._buf += (await self._read(size - len(self._buf))) _buf, self._buf = self._buf[:size], self._buf[size:] return _buf async def handle(self, client_reader, client_writer): try: await self._handle(client_reader, client_writer) except Exception as e: self.logger.error(repr(e)) self.logger.error(traceback.format_exc()) client_writer.close() async def _handle(self, client_reader, client_writer): self.client_address = client_writer.get_extra_info('peername') self.client_reader = client_reader self.logger.debug('incoming connection {}'.format(self.client_address)) try: fut = self.client_reader.readexactly(self.encryptor._iv_len) iv = await asyncio.wait_for(fut, timeout=10) self.encryptor.decrypt(iv) except IVError: self.logger.error('iv reused, {}'.format(self.client_address)) await self.play_dead() return except (asyncio.TimeoutError, asyncio.IncompleteReadError): self.logger.warning('iv read failed, {}'.format( self.client_address)) return try: fut = self.read(1) cmd = await asyncio.wait_for(fut, timeout=10) except asyncio.TimeoutError: self.logger.warning('read cmd timed out. {}'.format( self.client_address)) return except (ConnectionResetError, asyncio.IncompleteReadError): return except InvalidTag: self.logger.error('InvalidTag while read cmd. {}'.format( self.client_address)) await self.play_dead() return cmd = cmd[0] self.logger.debug('cmd: {} {}'.format(cmd, self.client_address)) if cmd in (1, 3, 4): # A shadowsocks request result = await self.handle_ss(client_reader, client_writer, addr_type=cmd) if result: await self.play_dead() return else: # TODO: security self.logger.error('bad cmd: %s, %s' % (cmd, self.client_address)) await self.play_dead() return async def play_dead(self, timeout=1): for _ in range(10): fut = self.client_reader.read(self.bufsize) try: await asyncio.wait_for(fut, timeout=1) except (asyncio.TimeoutError, ConnectionResetError): return async def open_connection(self, addr, port, proxy): # do security check here data = await self.request_is_loopback(addr) if data: raise ValueError('connect to localhost denied! {}'.format( self.client_address)) # create connection if proxy: fut = asyncio.open_connection(proxy[0], proxy[1]) remote_reader, remote_writer = await asyncio.wait_for(fut, timeout=5) s = 'CONNECT {0}:{1} HTTP/1.1\r\nHost: {0}:{1}\r\n\r\n'.format( addr, port) remote_writer.write(s.encode()) data = await remote_reader.readuntil(b'\r\n\r\n') if b'200' not in data: raise IOError(0, 'create tunnel via %s failed!' % proxy) return remote_reader, remote_writer fut = asyncio.open_connection(addr, port) remote_reader, remote_writer = await asyncio.wait_for(fut, timeout=5) return remote_reader, remote_writer async def handle_ss(self, client_reader, client_writer, addr_type): # if error, return 1 # get header... try: assert addr_type in (1, 3, 4) if addr_type & 15 == 1: addr = await self.read(4) addr = socket.inet_ntoa(addr) elif addr_type & 15 == 3: data = await self.read(1) addr = await self.read(data[0]) addr = addr.decode('ascii') else: data = await self.read(16) addr = socket.inet_ntop(socket.AF_INET6, data) port = await self.read(2) port, = struct.unpack('>H', port) except Exception as e: self.logger.error('error on read ss header: {} {}'.format( e, self.client_address)) self.logger.error(traceback.format_exc()) return 1 self.logger.info('connect to {}:{} {!r} {!r}'.format( addr, port, self.client_address, self.server.proxy)) try: remote_reader, remote_writer = await self.open_connection( addr, port, self.server.proxy) except Exception as e: self.logger.error('connect to {}:{} failed! {!r}'.format( addr, port, e)) return context = ForwardContext() tasks = [ self.ss_forward_A(client_reader, remote_writer, self.encryptor.decrypt, context), self.ss_forward_B(remote_reader, client_writer, self.encryptor.encrypt, context), ] try: await asyncio.wait(tasks) except Exception as e: self.logger.error(repr(e)) self.logger.error(traceback.format_exc()) remote_writer.close() async def ss_forward_A(self, read_from, write_to, cipher, context, timeout=60): # data from ss client while True: try: fut = self.read() data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time( ) - context.last_active > timeout or context.remote_eof: data = b'' else: continue except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, ConnectionResetError, OSError): data = b'' if not data: break try: write_to.write(data) await write_to.drain() except ConnectionResetError: context.local_eof = True return context.local_eof = True try: write_to.write_eof() except (ConnectionResetError, OSError): pass async def ss_forward_B(self, read_from, write_to, cipher, context, timeout=60): # data from remote while True: try: fut = read_from.read(self.bufsize) data = await asyncio.wait_for(fut, timeout=5) context.last_active = time.time() except asyncio.TimeoutError: if time.time( ) - context.last_active > timeout or context.local_eof: data = b'' else: continue except (ConnectionResetError, OSError): data = b'' if not data: break data = cipher(data) try: write_to.write(data) await write_to.drain() except ConnectionResetError: context.remote_eof = True return context.remote_eof = True # write_to.write_eof() async def get_ip_address(self, host): try: return ipaddress(host) except Exception: try: return ipaddress((await self.loop.getaddrinfo(host))[0][4][1]) except Exception: return ipaddress('0.0.0.0') async def request_is_loopback(self, addr): try: ip = await self.get_ip_address(addr) self.logger.debug('requesting {}'.format(ip)) if ip.is_loopback: return ip except Exception: pass