async def __add_client(self, client_id, client_name, sock_stream): msg = f'ADD_CLIENT: {client_name}(CID:{client_id}) ... ' if not validate_id_string(client_id, Protocol.CID_LENGTH): raise ProxyError(msg + f'[ERROR] (invalid client id provided.)') if client_id in self.__clients: if self.__clients[client_id].status == RunningStatus.STOPPED: del self.__clients[client_id] else: raise ProxyError(msg + f'[ERROR] (client id already registered.)') self.__clients[client_id] = _PeerClient(self.server_host, sock_stream, client_id, client_name) log.success(msg + '[OK]')
def execute(self, loop, api_name, *args, timeout=None): """ 客户端及其代理端口的操作接口。以一般调用的方式执行协程函数功能。 :param loop: :param api_name: :param args: :param timeout: :return: """ tcp_api_list = (self.AsyncApi.PAUSE_TCP_MAP, self.AsyncApi.RESUME_TCP_MAP, self.AsyncApi.REMOVE_TCP_MAP) udp_api_list = (self.AsyncApi.PAUSE_UDP_MAP, self.AsyncApi.RESUME_UDP_MAP, self.AsyncApi.REMOVE_UDP_MAP) client_api_list = (self.AsyncApi.PAUSE_CLIENT, self.AsyncApi.RESUME_CLIENT) client_api_list2 = (self.AsyncApi.REMOVE_CLIENT,) server_api_list = (self.AsyncApi.STARTUP_SERVER, self.AsyncApi.SHUTDOWN_SERVER) try: if api_name in tcp_api_list + udp_api_list: client_object = None server_port = args[0] for c in self.__clients: if api_name in tcp_api_list and server_port in self.__clients[c].tcp_maps or \ api_name in udp_api_list and server_port in self.__clients[c].udp_maps: client_object = self.__clients[c] break if client_object is None: raise ProxyError(f'proxy port{server_port} not found.') asyncio.run_coroutine_threadsafe( methodcaller(api_name, server_port)(client_object), loop=loop ).result(timeout=timeout) elif api_name in client_api_list: client_id = args[0] if client_id not in self.__clients: raise ProxyError(f'client with cid({client_id}) not found.') asyncio.run_coroutine_threadsafe( methodcaller(api_name)(self.__clients[client_id]), loop=loop ).result(timeout=timeout) elif api_name in client_api_list2: client_id = args[0] if client_id not in self.__clients: raise ProxyError(f'client with cid({client_id}) not found.') asyncio.run_coroutine_threadsafe( methodcaller(api_name, client_id)(self), loop=loop ).result(timeout=timeout) elif api_name in server_api_list: asyncio.run_coroutine_threadsafe( methodcaller(api_name)(self), loop=loop ).result(timeout=timeout) else: raise ProxyError(f'{api_name} not supported.') except ProxyError as e: raise ProxyError(f'Call of API({api_name}) failed as {e}')
async def add_udp_map(self, server_port, client_port): if server_port in self.udp_maps: raise ProxyError(self._map_msg(server_port, client_port, 'ERROR', f'already registered.')) if not check_udp_port_available(server_port): raise ProxyError(self._map_msg(server_port, client_port, 'ERROR', f'target port is in use.')) self.udp_maps[server_port] = { UdpMapInfo.CLIENT_PORT: client_port, UdpMapInfo.UDP_SOCKET: None, UdpMapInfo.SWITCH: True, UdpMapInfo.STATISTIC: [0, 0], UdpMapInfo.CREATE_TIME: datetime.utcnow(), UdpMapInfo.TASK_TRANSFER: asyncio.create_task(self.__udp_service_task(server_port)), } log.success(self._map_msg(server_port, client_port, 'OK'))
async def add_tcp_map(self, server_port, client_port): if server_port in self.tcp_maps: raise ProxyError(self._map_msg(server_port, client_port, 'ERROR', f'already registered.')) if check_listening(self.server_host, server_port): raise ProxyError(self._map_msg(server_port, client_port, 'ERROR', f'target port is in use.')) self.tcp_maps[server_port] = { TcpMapInfo.CLIENT_PORT: client_port, TcpMapInfo.SWITCH: True, TcpMapInfo.STATISTIC: [0, 0], TcpMapInfo.CREATE_TIME: datetime.utcnow(), TcpMapInfo.CONN_POOL: {}, TcpMapInfo.TASK_LISTEN: asyncio.create_task(self.__tcp_service_task(server_port)), } log.success(self._map_msg(server_port, client_port, 'OK'))
async def remove_udp_map(self, client_port): if client_port not in self.udp_maps: raise ProxyError( self._map_msg(client_port, None, 'ERROR', f'not registered.')) else: server_port = self.udp_maps[client_port][UdpMapInfo.SERVER_PORT] status, detail = (await self.__protocol.request( Protocol.ClientCommand.REMOVE_UDP_MAP, f'{self.udp_maps[client_port][UdpMapInfo.SERVER_PORT]}', Protocol.CONNECTION_TIMEOUT)).split(Protocol.PARAM_SEPARATOR, 1) if status != Protocol.Result.SUCCESS: raise ProxyError(self._map_msg(client_port, server_port, 'ERROR'), detail) del self.udp_maps[client_port] log.success(self._map_msg(client_port, server_port, 'OK'))
async def remove_client(self, client_id): if client_id not in self.__clients: raise ProxyError(f'REMOVE_CLIENT: (None)(CID:{client_id}) ... [ERROR] (not registered.)') if self.__clients[client_id].status != RunningStatus.STOPPED: await self.__clients[client_id].close_client(wait=True) log.success(f'REMOVE_CLIENT: {self.__clients[client_id].client_name}(CID:{client_id}) ... [OK]') del self.__clients[client_id]
async def remove_udp_map(self, server_port): if server_port not in self.udp_maps: raise ProxyError(self._map_msg(server_port, None, 'ERROR', f'not registered.')) await self.pause_udp_map(server_port) self.udp_maps[server_port][UdpMapInfo.UDP_SOCKET].close() self.udp_maps[server_port][UdpMapInfo.TASK_TRANSFER].cancel() log.success(self._map_msg(server_port, self.udp_maps[server_port][UdpMapInfo.CLIENT_PORT], 'OK')) del self.udp_maps[server_port]
async def remove_tcp_map(self, server_port): if server_port not in self.tcp_maps: raise ProxyError(self._map_msg(server_port, None, 'ERROR', f'not registered.')) await self.pause_tcp_map(server_port) for stream in self.tcp_maps[server_port][TcpMapInfo.CONN_POOL]: stream[1].close() self.tcp_maps[server_port][TcpMapInfo.TASK_LISTEN].cancel() log.success(self._map_msg(server_port, self.tcp_maps[server_port][TcpMapInfo.CLIENT_PORT], 'OK')) del self.tcp_maps[server_port]
async def add_tcp_map(self, client_port, server_port): if client_port in self.tcp_maps: raise ProxyError( self._map_msg(client_port, server_port, 'ERROR', f'already registered.')) status, detail = (await self.__protocol.request( Protocol.ClientCommand.ADD_TCP_MAP, f'{client_port}{Protocol.PARAM_SEPARATOR}{server_port}', Protocol.CONNECTION_TIMEOUT)).split(Protocol.PARAM_SEPARATOR, 1) if status != Protocol.Result.SUCCESS: raise ProxyError( self._map_msg(client_port, server_port, 'ERROR', detail)) self.tcp_maps[client_port] = { TcpMapInfo.SERVER_PORT: server_port, TcpMapInfo.SWITCH: True, TcpMapInfo.STATISTIC: [0, 0], TcpMapInfo.CREATE_TIME: datetime.utcnow(), } log.success(self._map_msg(client_port, server_port, 'OK'))
async def __serve_request_task(self): while True: uuid, cmd, data = await self.__protocol.get_request(timeout=None) result = Protocol.PARAM_SEPARATOR.join( (Protocol.Result.SUCCESS, '')) try: if cmd == Protocol.Command.PING: result += datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') elif cmd == Protocol.ServerCommand.ADD_TCP_CONNECTION: client_port = int(data) server_port = self.tcp_maps[client_port][ TcpMapInfo.SERVER_PORT] reader1, writer1 = await asyncio.open_connection( '127.0.0.1', client_port) reader2, writer2 = await asyncio.open_connection( self.server_host, self.server_port) conn_uuid = uuid4().hex identify = Protocol.PARAM_SEPARATOR.join( (Protocol.ConnectionType.PROXY_TCP_DATA, self.client_id, str(server_port), conn_uuid)) writer2.write((identify + '\n').encode()) await writer2.drain() resp = (await reader2.readline()).decode().strip() if not resp or resp[:len(Protocol.Result.SUCCESS )] != Protocol.Result.SUCCESS: raise ProxyError( 'Add tcp connection failed while connecting to server.' ) asyncio.create_task( self.__tcp_data_relay_task(client_port, (reader1, writer1), (reader2, writer2))) result += conn_uuid else: log.warning( f'Unrecognised request from Server: {Protocol.make_req(uuid, cmd, data)}' ) result = Protocol.PARAM_SEPARATOR.join( (Protocol.Result.INVALID, 'unrecognised request')) except Exception as e: log.error( f'Error while processing request({Protocol.make_req(uuid, cmd, data)}): {e}' ) result = Protocol.PARAM_SEPARATOR.join( (Protocol.Result.ERROR, str(e))) finally: try: await self.__protocol.send_response(uuid, cmd, result) except Exception as e: log.error(e) break
async def __process_conn_task(self): while True: try: server_port, reader, writer = await self.__conn_queue.get() except Exception as e: log.error(e) break else: self.__conn_queue.task_done() try: client_port = self.tcp_maps[server_port][TcpMapInfo.CLIENT_PORT] result = await self.__protocol.request( Protocol.ServerCommand.ADD_TCP_CONNECTION, client_port, timeout=Protocol.CONNECTION_TIMEOUT) status, detail = result.split(Protocol.PARAM_SEPARATOR, 1) if status == Protocol.Result.SUCCESS: conn_uuid = detail else: raise ProxyError(detail) wait_time = 0 while wait_time < Protocol.CONNECTION_TIMEOUT: if conn_uuid in self.tcp_maps[server_port][TcpMapInfo.CONN_POOL]: break await asyncio.sleep(Protocol.TASK_SCHEDULE_PERIOD) wait_time += Protocol.TASK_SCHEDULE_PERIOD if conn_uuid not in self.tcp_maps[server_port][TcpMapInfo.CONN_POOL]: raise ProxyError(f'{self.client_id}({client_port}) ----> Server({server_port}) ... [TIMEOUT].') reader2, writer2 = self.tcp_maps[server_port][TcpMapInfo.CONN_POOL][conn_uuid] del self.tcp_maps[server_port][TcpMapInfo.CONN_POOL][conn_uuid] asyncio.create_task(self.__tcp_data_relay_task(server_port, (reader, writer), (reader2, writer2))) except Exception as e: log.error(e) writer.close()
async def __dispatch_datagram_task(self): while True: try: data, address = self.__udp_socket.recvfrom(Protocol.SOCKET_BUFFER_SIZE) except BlockingIOError: await asyncio.sleep(Protocol.TASK_SCHEDULE_PERIOD) continue except Exception as e: log.error(e) break try: try: packet_info = Protocol.unpack_udp_packet(data, unpack_data=False) except Exception as e: raise ProxyError(f'Protocol.unpack_udp_packet(): {e}') if packet_info[Protocol.UdpPacketInfo.TYPE] == Protocol.UdpPacketType.SYNC: cid = packet_info['client_id'] timestamp = packet_info[Protocol.UdpPacketInfo.TIMESTAMP] if cid not in self.__clients: raise ProxyError(f'Received udp sync packet from client(CID:{cid}) which is not found.') client = self.__clients[cid] log.debug(f'Received udp sync packet from {client.client_name}(CID:{cid}) at {timestamp}') client.udp_statistic(0, len(data)) client.udp_e_address = address if client.status == RunningStatus.RUNNING: sync_packet = Protocol.pack_udp_sync_packet(server_id=self.server_id) try: self.__udp_socket.sendto(sync_packet, address) except IOError as e: log.error(e) break client.udp_statistic(len(sync_packet), 0) elif packet_info[Protocol.UdpPacketInfo.TYPE] == Protocol.UdpPacketType.DATA: server_port = packet_info[Protocol.UdpPacketInfo.SERVER_PORT] user_address = packet_info[Protocol.UdpPacketInfo.USER_ADDRESS] client = None for cid in self.__clients: if server_port in self.__clients[cid].udp_maps: client = self.__clients[cid] client.udp_statistic(0, Protocol.UDP_DATA_HEADER_LEN) client.udp_maps[server_port][UdpMapInfo.STATISTIC][1] += \ len(data) - Protocol.UDP_DATA_HEADER_LEN break if not client: raise ProxyError(f'Received udp data packet which is not owned by any client.') if client.status == RunningStatus.RUNNING and client.udp_maps[server_port][UdpMapInfo.SWITCH]: udp_socket = client.udp_maps[server_port][UdpMapInfo.UDP_SOCKET] try: udp_socket.sendto(data[Protocol.UDP_DATA_HEADER_LEN:], user_address) except IOError as e: log.error(e) continue log.debug(f'Forwards udp data packet from port({server_port}) to {user_address}') else: log.warning(f'Drops udp data packet on port({server_port}) as switch is turned off.') else: raise ProxyError(f'Received udp packet from {address} with unknown type.') except Exception as e: log.debug(f'Received udp packet from: {address}, data:\n' + data.hex()) log.warning(e)
async def __process_conn_task(self): async def stream_readline(stream_reader): return await stream_reader.readline() while True: try: reader, writer = await self.__conn_queue.get() except Exception as e: log.error(e) break else: self.__conn_queue.task_done() try: task = asyncio.create_task(stream_readline(reader)) await asyncio.wait_for(task, timeout=Protocol.CONNECTION_TIMEOUT) identify = task.result().decode().strip() if not identify: peer = writer.get_extra_info('peername') raise ProxyError(f'Identify string is empty of tcp connection from {peer}') log.debug(identify) except Exception as e: log.error(e) writer.close() continue reject_reason = 'unknown error' try: conn_type, data = identify.split(Protocol.PARAM_SEPARATOR, 1) val_list = data.split(Protocol.PARAM_SEPARATOR) if conn_type == Protocol.ConnectionType.MANAGER: reject_reason = 'role of MANAGER is not supported yet.' raise NotImplementedError('Reject manager connection as ' + reject_reason) elif conn_type == Protocol.ConnectionType.PROXY_CLIENT: client_id = val_list[0] client_name = val_list[1] if not client_id: # generate new client id if it is empty while True: client_id = uuid4().hex[-Protocol.CID_LENGTH:].upper() if client_id not in self.__clients: break try: await self.__add_client(client_id, client_name, (reader, writer)) except Exception as e: reject_reason = str(e) raise writer.write(f'{Protocol.Result.SUCCESS}{Protocol.PARAM_SEPARATOR}{client_id}\n'.encode()) await writer.drain() elif conn_type == Protocol.ConnectionType.PROXY_TCP_DATA: client_id = val_list[0] server_port = int(val_list[1]) conn_id = val_list[2] if client_id not in self.__clients: reject_reason = f'provided client_id({client_id}) not found.' raise ProxyError('Reject reverse-data-connection (tcp) as ' + reject_reason) else: client = self.__clients[client_id] if client.status != RunningStatus.RUNNING: reject_reason = f'{client.client_name}(CID:{client.client_id}) is paused.' raise ProxyError('Reject reverse-data-connection (tcp) as ' + reject_reason) if not client.tcp_maps[server_port][TcpMapInfo.SWITCH]: reject_reason = f'server_port({server_port}) is paused.' raise ProxyError('Reject reverse-data-connection (tcp) as ' + reject_reason) client.tcp_maps[server_port][TcpMapInfo.CONN_POOL][conn_id] = (reader, writer) writer.write(f'{Protocol.Result.SUCCESS}{Protocol.PARAM_SEPARATOR}\n'.encode()) await writer.drain() else: reject_reason = 'unrecognised connection type' raise ProxyError(f'Reject connection on port({self.server_port}) with identify: {identify}') except Exception as e: log.error(e) writer.write(f'{Protocol.Result.ERROR}{Protocol.PARAM_SEPARATOR}{reject_reason}\n'.encode()) await writer.drain() writer.close()
async def __udp_receive_task(self): def udp_send_data(__b_data, __udp_port, __user_address): key = hash((__udp_port, __user_address)) if key in self.__udp_req_map: sock = self.__udp_req_map[key][2] self.__udp_req_map[key][3] = time.time() else: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setblocking(False) self.__udp_req_map[key] = [ __udp_port, user_address, sock, time.time() ] sock.sendto(__b_data, ('127.0.0.1', __udp_port)) while True: try: data, address = self.__udp_socket.recvfrom( Protocol.SOCKET_BUFFER_SIZE) except BlockingIOError: await asyncio.sleep(Protocol.TASK_SCHEDULE_PERIOD) continue except IOError as e: log.error(e) break try: # if address[0] != self.server_host: # raise ProxyError(f'Received udp packet from {address} which is not the server.') try: packet_info = Protocol.unpack_udp_packet(data, unpack_data=False) except Exception as e: raise ProxyError(f'Protocol.unpack_udp_packet(): {e}') if packet_info[Protocol.UdpPacketInfo. TYPE] == Protocol.UdpPacketType.SYNC: self.udp_statistic(0, len(data)) self.__udp_ping = time.time() log.debug( f'UDP_PING: {packet_info[Protocol.UdpPacketInfo.TIMESTAMP]}' ) elif packet_info[Protocol.UdpPacketInfo. TYPE] == Protocol.UdpPacketType.DATA: self.udp_statistic(0, Protocol.UDP_DATA_HEADER_LEN) self.udp_maps[UdpMapInfo.STATISTIC][1] += len( data) - Protocol.UDP_DATA_HEADER_LEN user_address = packet_info[ Protocol.UdpPacketInfo.USER_ADDRESS] server_port = packet_info[ Protocol.UdpPacketInfo.SERVER_PORT] client_port = None for port in self.udp_maps: if self.udp_maps[port][ UdpMapInfo.SERVER_PORT] == server_port: client_port = port break if not client_port: log.warning( f'Received udp data packet on server port({server_port}) that not registered.' ) continue try: udp_send_data(data[Protocol.UDP_DATA_HEADER_LEN:], client_port, user_address) except IOError as e: log.error(e) continue log.debug( f'Received udp data packet from {user_address} on port({client_port})' ) else: self.udp_statistic(0, len(data)) raise ProxyError( f'Received udp packet from {address} with unknown type.' ) except Exception as e: log.debug(f'Received udp packet from: {address}, data:\n' + data.hex()) log.warning(e) self.__protocol.close()
async def resume_client(self): msg = f'RESUME_CLIENT: {self.client_name}(CID:{self.client_id}) ... ' if self.status == RunningStatus.STOPPED: raise ProxyError(msg + '[ERROR] (already stopped.)') self.status = RunningStatus.RUNNING log.success(msg + '[OK]')
async def resume_udp_map(self, server_port): if server_port not in self.udp_maps: raise ProxyError(self._map_msg(server_port, None, 'ERROR', f'not registered.')) self.udp_maps[server_port][UdpMapInfo.SWITCH] = True log.success(self._map_msg(server_port, self.udp_maps[server_port][UdpMapInfo.CLIENT_PORT], 'OK'))
async def pause_tcp_map(self, server_port): if server_port not in self.tcp_maps: raise ProxyError(self._map_msg(server_port, None, 'ERROR', f'not registered.')) self.tcp_maps[server_port][TcpMapInfo.SWITCH] = False log.success(self._map_msg(server_port, self.tcp_maps[server_port][TcpMapInfo.CLIENT_PORT], 'OK'))